您的位置:首页 > 数据库

数据库学习第二季第五集:编程语言的存储过程和函数机制及其编程语言的调用学习总结

2017-11-25 00:00 721 查看
摘要: MySQL存储过程(Stored procedure)以及函数(function)为两种MySQL编程中的常用手段,合理使用这两者可以减少重复编写查询语句的次数。此外对于MySQL查询,存储过程和函数还有安全控制以及高效实现的特点。因此有关存储过程和函数的学习也是学习MySQL编程的必修课。

MySQL存储过程和函数简介

MySQL的存储过程和函数总计有四类,即不返回变量的存储过程、返回单变量或多变量的存储过程、函数和返回查询表的存储过程。对于每一类不同的存储过程或函数,不同的编程语言都有不同的调用机制。限于篇幅,本文仅选用面向对象编程语言的代表C++语言和脚本语言代表Python演示MySQL存储过程和函数的程序调用。Java代码的调用作为附录附于本文附录,欢迎Java爱好者进行参考。

书写MySQL存储过程的基本语法为:

//Change the delimiter and will be changed back at end:
delimiter //
CREATE PROCEDURE proc_name([IN|OUT|INOUT para dataType], ...)
BEGIN
# Write procedure here, maybe select, insert, create, update or delete operations.
END //
delimiter ;

其中,存储过程的参数列表中每一个参数需要被声明IN、OUT或者INOUT。IN表示该数据仅为传入参数,修改过程对调用该过程的程序不透明;OUT和INOUT同义,表示数据为返回参数,可能在存储过程中被修改,修改结果返回调用该过程的程序。para为参数的变量名,dataType即常用MySQL允许的数据类型。

MySQL函数可以理解为有一个OUT参数、0-多个IN参数形成的仅供查询选择单变量结果的一种特殊的存储过程,书写MySQL函数的基本语法为:

delimiter //
CREATE FUNCTION avgSalary([para dataType], ...)
RETURNS dataType
BEGIN
# Put Selection operation here
RETURN para;
END //
delimiter ;

参数的理解类似存储过程。

本文所用数据库用例结构

本文引用博文《各种编程语言从数据库中获得数据方式小结》的数据库结构,详见:https://my.oschina.net/Samyan/blog/1577680

不返回变量的存储过程书写和程序调用示例:

此类存储过程往往用于程序快速调用执行简单的插入、删除以及修改操作。

delimiter //
CREATE PROCEDURE add_department(IN DepartmentName VARCHAR(50),
IN DepartmentFloor TINYINT, IN DepartmentPhone 	CHAR(11), IN ManagerID SMALLINT)
BEGIN
SELECT MAX(DepartmentID) INTO @num_of_depts
FROM department;

INSERT INTO department VALUE(@num_of_depts + 1, DepartmentName, DepartmentFloor,
DepartmentPhone, ManagerID);
END //
delimiter ;

此类存储过程在各编程语言中调用示例如下:

被Python调用:

def exec_proc_without_ret():
proc_no_ret_query = """CALL add_department('Food', 6, '3248', 2)"""
try:
cnn = connector.connect(host='localhost', user='root', password='123456', database='department_store')
cursor = cnn.cursor()
cursor.execute(proc_no_ret_query)
cnn.commit()
except Exception as e:
raise e
finally:
cnn.close()
cursor.close()

被C++调用:

//Example of procedure returns nothing
void callNoRetProcedure() {
try {
sql::mysql::MySQL_Driver *driver;
driver = sql::mysql::get_mysql_driver_instance();
sql::Connection *con;
con = driver->connect("tcp://127.0.0.1:3306", "root", "123456");
con->setSchema("department_store");
sql::Statement *stmt;
stmt = con->createStatement();
stmt->execute("CALL add_department('Food', 6, '3248', 2)");
delete stmt;
delete con;
}
catch (sql::SQLException &e) { throw e; }
}//end function

返回单或多变量的存储过程:

此类存储过程往往用于程序调用比较复杂的MySQL操作(比如既要插入又要返回关于插入的情况)。

/* For creating a procedure, IN means that outer caller pass the variable to the procedure,
* And the procedure deal with it without returning results.
* Out means outter variable pass a variable to the procedure (possibly null)
* And the procedure deal with it to return values.
* INOUT is similar to OUT, except that the parameter might be initialized.*/
delimiter //
CREATE PROCEDURE add_employee(IN EmployeeName VARCHAR(50), IN EmployeeSalary DECIMAL(8, 2), IN DepartmentID SMALLINT,
IN BossID SMALLINT, IN HiredDate DATETIME, OUT EmpID SMALLINT)
BEGIN
SELECT MAX(EmployeeID) + 1 INTO EmpID
FROM employee;
INSERT INTO employee VALUES (EmpID, EmployeeName, EmployeeSalary, DepartmentID, BossID, HiredDate);
END //
delimiter ;

此类存储过程调用示例如下:

被Python调用:

def exec_proc_with_single_ret():
proc_single_ret_query = """CALL add_employee('Johney', 2500.0, 3, 1, NOW(), @id)"""
try:
cnn = connector.connect(host='localhost', user='root', password='123456', database='department_store')
cursor = cnn.cursor()
cursor.execute(proc_single_ret_query)
cursor.execute("SELECT @id AS employeeNum")
for eachDatum in cursor:
numEmployee = eachDatum[0]
print "Number of current employees", numEmployee
cursor.close()
cnn.commit()
except Exception as e:
raise e
finally:
cnn.close()
cursor.close()

被C++调用:

//Example of procedures returns a single variable / mutiple variables
void callSingleRetProcedure() {
try {
sql::mysql::MySQL_Driver *driver;
driver = sql::mysql::get_mysql_driver_instance();
sql::Connection *con;
sql::ResultSet *retSet;
con = driver->connect("tcp://127.0.0.1:3306", "root", "123456");
con->setSchema("department_store");
sql::Statement *stmt;
stmt = con->createStatement();
stmt->execute("CALL add_employee('Johney', 2500.0, 3, 1, NOW(), @id)");
retSet = stmt->executeQuery("SELECT @id AS employeeNum");
while (retSet->next())
cout << "Number of employees now: " << retSet->getString("employeeNum") << endl;
delete stmt;
delete con;
}
catch (sql::SQLException &e) { throw e; }
}//end function

MySQL函数

MySQL函数的功能类似于返回单变量且仅实现查询功能的存储过程,例如,以下函数返回特定id的员工平均薪资:

delimiter //
CREATE FUNCTION `avgSalary`(EmpID1 SMALLINT, EmpID2 SMALLINT) RETURNS decimal(8,2)
BEGIN
SELECT AVG(EmployeeSalary) INTO @avg_salary
FROM employee
WHERE EmployeeID between EmpID1 AND EmpID2;
RETURN @avg_salary;
END //
delimiter ;

函数在各编程语言中调用示例如下:

此处的示例为采用动态SQL机制的示例。然而,无论静态还是动态SQL都可以调用存储过程以及MySQL函数。

被Python调用:

def exec_func():
func_query = """SELECT avgSalary(%s, %s) AS avg_salary"""
try:
cnn = connector.connect(host='localhost', user='root', password='123456', database='department_store')
cursor = cnn.cursor(prepared=True)
cursor.execute(func_query, (1, 13))
for eachDatum in cursor:
avg_salary = eachDatum[0]
print "Avg salaries of employee whose id btw 1 and 12:", avg_salary
cursor.close()
except Exception:
raise Exception
finally:
cnn.close()
cursor.close()

被C++调用:

//Example of calling function, prepared statements can also be used in procedure calls and function calls.
void callFunction() {
try {
sql::mysql::MySQL_Driver *driver;
driver = sql::mysql::get_mysql_driver_instance();
sql::Connection *con;
sql::ResultSet *retSet;
con = driver->connect("tcp://127.0.0.1:3306", "root", "123456");
con->setSchema("department_store");
sql::PreparedStatement *prep_stmt;
prep_stmt = con->prepareStatement("SELECT avgSalary(1, 12) AS avg_salary");
retSet = prep_stmt->executeQuery();
while (retSet->next())
cout << "Average salary of employees from 1 to 12: " << retSet->getString("avg_salary") << endl;
delete retSet;
delete prep_stmt;
delete con;
}
catch (sql::SQLException &e) { throw e; }
}//end function

返回值为单个或多个查询表的存储过程:

此类存储过程一般用于获取需要多表联合获取查询数据的数据库操作。

delimiter //
CREATE PROCEDURE get_emp_data()
BEGIN
SELECT Emp.EmployeeID, Emp.EmployeeName, Emp.EmployeeSalary, DepartmentName,
Boss.EmployeeName AS Boss, Emp.HireDate
FROM employee Emp NATURAL JOIN Department Dept LEFT OUTER JOIN employee Boss
ON Emp.BossID = Boss.EmployeeID
ORDER BY Emp.EmployeeID;
END //
delimiter ;

此类存储过程调用示例如下:

被Python调用:

Python调用返回查询表的存储过程机制非常特殊,需要存储结果到buffer中,而使用callproc才能启用存储该结果的buffer。

def exec_proc_table_ret():
try:
cnn = connector.connect(host='localhost', user='root', password='123456', database='department_store')
cursor = cnn.cursor(dictionary=True, buffered=True)
cursor.callproc('get_emp_data', args=())

for dataSet in cursor.stored_results():
for datum in dataSet.fetchall():
print datum
cursor.close()
except Exception as e:
raise e
finally:
cnn.close()
cursor.close()

被C++调用:

//Example of procedures returns a table of query
void callRetSetProcedure() {
try {
sql::mysql::MySQL_Driver *driver;
driver = sql::mysql::get_mysql_driver_instance();
sql::Connection *con;
con = driver->connect("tcp://127.0.0.1:3306", "root", "123456");
sql::Statement *stmt;
sql::ResultSet *retSet;
stmt = con->createStatement();
stmt->execute("USE department_store");
retSet = stmt->executeQuery("CALL get_emp_data()");
cout << "EmployeeID " << "EmployeeName " << "EmployeeSalary " << "DepartmentName " << "Boss " << "HireDate" << endl;
while (retSet->next()) {
cout << retSet->getInt(1) << ' ' << retSet->getString(2) << ' '
<< retSet->getDouble(3) << ' ' << retSet->getString(4) << ' '
<< retSet->getString(5) << ' ' << retSet->getString(6) << endl;
}
delete retSet;
delete stmt;
delete con;
delete driver;
}
catch (sql::SQLException &e) {
throw e;
}
}//end function

参考资料:

Python中MySQLConnector模块使用方法详解,URL:http://www.111cn.net/phper/python/67319.htm

“connector-cpp-en.a4.pdf.” .

“connector-odbc-en.a4.pdf.” .

tutorialspoint.com, “JDBC - Create Database Example,” www.tutorialspoint.com. [Online]. Available: https://www.tutorialspoint.com/jdbc/jdbc-create-database.htm. [Accessed: 16-Nov-2017].

“MySQL :: MySQL 5.7 Reference Manual :: 27.8.6 C API Function Overview.” [Online]. Available: https://dev.mysql.com/doc/refman/5.7/en/c-api-function-overview.html. [Accessed: 17-Nov-2017].

王珊和萨师煊,《数据库系统概论》第5版,高等教育出版社,2016年2月,北京

附录福利:MySQL Trigger机制以及Function的Java程序调用示例

//视IDE情况不同选择使用
package mysqltrigger;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class MySQLTrigger {

private Connection connect = null;
private Statement statement = null;
private ResultSet retSet = null;
private PreparedStatement prep_stmt = null;

public static void main(String[] args) {
// TODO code application logic here
MySQLTrigger tg = new MySQLTrigger();
tg.callNoRetProcedure();
tg.callSingleRetProcedure();
tg.callFunction();
tg.callRetSetProcedure();
}//end main method

public void callNoRetProcedure() {
try {
// this will load the MySQL driver, each DB has its own driver
Class.forName("com.mysql.jdbc.Driver");
// setup the connection with the DB.
connect = DriverManager.getConnection(
"jdbc:mysql://localhost/department_store",
"root", "123456");

// statements allow to issue SQL queries to the database
statement = connect.createStatement();
// resultSet gets the result of the SQL query
statement.execute("CALL add_department('Food', 6, '3248', 2)");
statement.close();
connect.close();

} catch (SQLException | ClassNotFoundException ex) {
ex.printStackTrace();
}//end try-catch
}

public void callSingleRetProcedure() {
try {
// this will load the MySQL driver, each DB has its own driver
Class.forName("com.mysql.jdbc.Driver");
// setup the connection with the DB.
connect = DriverManager.getConnection(
"jdbc:mysql://localhost/department_store",
"root", "123456");

// statements allow to issue SQL queries to the database
statement = connect.createStatement();
// resultSet gets the result of the SQL query
statement.executeQuery("CALL add_employee('Johney', 2500.0, 3, 1, NOW(), @id)");
retSet = statement.executeQuery("SELECT @id AS employeeNum");
while (retSet.next()) {
System.out.println("Number of current employees: " + retSet.getInt(1));
}
statement.close();
connect.close();

} catch (SQLException | ClassNotFoundException ex) {
ex.printStackTrace();
}//end try-catch
}//end method

public void callFunction() {
try {
// this will load the MySQL driver, each DB has its own driver
Class.forName("com.mysql.jdbc.Driver");
// setup the connection with the DB.
connect = DriverManager.getConnection(
"jdbc:mysql://localhost/department_store",
"root", "123456");

// resultSet gets the result of the SQL query
prep_stmt = connect.prepareStatement("SELECT avgSalary(?, ?) AS avg_salary");
prep_stmt.setInt(1, 1);
prep_stmt.setInt(2, 12);

retSet = prep_stmt.executeQuery();

if (retSet.next()) {
System.out.println("Average salary of employees id from 1 to 12: "
+ retSet.getDouble(1));
}

retSet.close();
prep_stmt.close();
connect.close();

} catch (SQLException | ClassNotFoundException ex) {
ex.printStackTrace();
}//end try-catch
}//end method

public void callRetSetProcedure() {
try {
// this will load the MySQL driver, each DB has its own driver
Class.forName("com.mysql.jdbc.Driver");
// setup the connection with the DB.
connect = DriverManager.getConnection(
"jdbc:mysql://localhost/department_store",
"root", "123456");

// statements allow to issue SQL queries to the database
statement = connect.createStatement();
// resultSet gets the result of the SQL query
retSet = statement.executeQuery("CALL get_emp_data()");
System.out.println("ID EmployeeName Salary Department Boss HiredDate");
while (retSet.next()) {
System.out.print(retSet.getInt(1) + " ");
System.out.print(retSet.getString(2) + " ");
System.out.print(retSet.getDouble(3) + " ");
System.out.print(retSet.getString(4) + " ");
System.out.print(retSet.getString(5) + " ");
System.out.println(retSet.getTimestamp(6));
/*//Or this is also ok, but returns Date only:
//System.out.println(retSet.getDate(6));*/
}
retSet.close();
statement.close();
connect.close();

} catch (SQLException | ClassNotFoundException ex) {
ex.printStackTrace();
}//end try-catch
}//end method
}//end class

程序的运行效果如图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息