储存过程的基本使用
DROP procedure IF EXISTS `getGameName`;#删除储存过程
# 设置结束符为$$,因为在begin,end之前的语句结束符是分号;
# 这样改一下可以使代码的可读性更强一点
DELIMITER $$
#注意参数名不能与字段名相同
CREATE PROCEDURE getGameName(
IN gameid INT, #入参
OUT g_name VARCHAR(45), #出参
OUT pin_yin VARCHAR(45)) #出参
BEGIN
SELECT gamename
INTO g_name
FROM cy_game
WHERE id = gameid;
SELECT pinyin
INTO pin_yin
FROM cy_game
WHERE id = gameid;
END$$
DELIMITER ;
call getGameName(4,@g_name,@pin_yin);#调用储存过程
SELECT @g_name,@pin_yin;#返回值
SHOW PROCEDURE STATUS; #显示有哪些储存过程
SHOW CREATE PROCEDURE getGameInfo;#显示指定储存过程的信息,包括代码
一. 定义
存储过程是存储在数据库目录中的一段声明性SQL语句。 触发器,其他存储过程以及Java,Python,PHP等应用程序可以调用存储过程。
自身的存储过程称为递归存储过程。大多数数据库管理系统支持递归存储过程。 但是,MySQL不支持它。 在MySQL中实现递归存储过程之前,您应该检查MySQL数据库的版本
二. 优缺点
MySQL是最受欢迎的开源RDBMS,被社区和企业广泛使用。 然而,在它发布的第一个十年期间,它不支持存储过程,存储函数,触发器和事件。自从MySQL 5.0版本以来,这些功能被添加到MySQL数据库引擎,使其更加灵活和强大。
MySQL存储过程的优点
通常存储过程有助于提高应用程序的性能。当创建,存储过程被编译之后,就存储在数据库中。 但是,MySQL实现的存储过程略有不同。 MySQL存储过程按需编译。 在编译存储过程之后,MySQL将其放入缓存中。 MySQL为每个连接维护自己的存储过程高速缓存。 如果应用程序在单个连接中多次使用存储过程,则使用编译版本,否则存储过程的工作方式类似于查询。
存储过程有助于减少应用程序和数据库服务器之间的流量,因为应用程序不必发送多个冗长的SQL语句,而只能发送存储过程的名称和参数。
存储的程序对任何应用程序都是可重用的和透明的。 存储过程将数据库接口暴露给所有应用程序,以便开发人员不必开发存储过程中已支持的功能。
存储的程序是安全的。 数据库管理员可以向访问数据库中存储过程的应用程序授予适当的权限,而不向基础数据库表提供任何权限。
除了这些优点之外,存储过程有其自身的缺点,在数据库中使用它们之前,您应该注意这些缺点。
MySQL存储过程的缺点
如果使用大量存储过程,那么使用这些存储过程的每个连接的内存使用量将会大大增加。 此外,如果您在存储过程中过度使用大量逻辑操作,则CPU使用率也会增加,因为数据库服务器的设计不当于逻辑运算。
存储过程的构造使得开发具有复杂业务逻辑的存储过程变得更加困难。
很难调试存储过程。只有少数数据库管理系统允许您调试存储过程。不幸的是,MySQL不提供调试存储过程的功能。
开发和维护存储过程并不容易。开发和维护存储过程通常需要一个不是所有应用程序开发人员拥有的专业技能。这可能会导致应用程序开发和维护阶段的问题。
内存,CPU开销大,复杂逻辑困难,不便调试,开发和维护不易
MySQL存储过程有自己的优点和缺点。开发应用程序时,您应该决定是否应该或不应该根据业务需求使用存储过程。
三. 创建储存过程,调用储存过程
- 使用命令行创建
#创建储存过程
DELIMITER $$
CREATE PROCEDURE GetAllProducts()
BEGIN
SELECT * FROM products;
END $$
DELIMITER ;
#调用储存过程
CALL GetAllProducts();
注:在第一次调用DELIMITER
可以不用加分号
- navicat客户端创建:
a) 打开数据库,左侧目录 ->函数->右击新建函数
b). 设置参数,入参,出参,出入参
c) 编写sql语句,保存,创建完毕
d) 调用储存过程
直接在相关的函数右击,点击运行储存过程,即可。如果需要参数,会弹出请输出参数的框,输入相关的参数,用逗号隔开即可。
四. 变量
声明变量
要在存储过程中声明一个变量,可以使用DECLARE语句,如下所示:
DECLARE variable_name datatype(size) DEFAULT default_value;
下面来更详细地解释上面的语句:
- 首先,在
DECLARE
关键字后面要指定变量名。变量名必须遵循MySQL表列名称的命名规则。 - 其次,指定变量的数据类型及其大小。变量可以有任何MySQL数据类型,如
INT
,VARCHAR
,DATETIME
等。 - 最后,当声明一个变量时,它的初始值为
NULL
。但是可以使用DEFAULT
关键字为变量分配默认值。
例如,可以声明一个名为total_sale
的变量,数据类型为INT
,默认值为0
,如下所示:
DECLARE total_sale INT DEFAULT 0;
MySQL允许您使用单个DECLARE
语句声明共享相同数据类型的两个或多个变量,如下所示:
DECLARE x, y INT DEFAULT 0;
注:这里和C/C++系列的有点不同,在C/C++里面,如果这样声明两个变量
int *a,b;
声明的是两个类型的变量,一个是指向一整数型的指针a,另一个是整型b。而在sql是,它声明的是一种类型的变量。
分配变量值
当声明了一个变量后,就可以开始使用它了。要为变量分配一个值,可以使用SET语句,例如:
DECLARE total_count INT DEFAULT 0;
SET total_count = 10;
上面语句中,分配total_count
变量的值为10
。除了SET
语句之外,还可以使用SELECT INTO
语句将查询的结果分配给一个变量。 请参阅以下示例:
DECLARE total_products INT DEFAULT 0
SELECT COUNT(*) INTO total_products
FROM products
在上面的例子中:
首先,声明一个名为total_products
的变量,并将其值初始化为0
。然后,使用SELECT INTO
语句来分配值给total_products
变量,从示例数据库(yiibaidb
)中的products
表中选择的产品数量。
变量范围(作用域)
一个变量有自己的范围(作用域),它用来定义它的生命周期。 如果在存储过程中声明一个变量,那么当达到存储过程的END
语句时,它将超出范围,因此在其它代码块中无法访问。
如果您在BEGIN END
块内声明一个变量,那么如果达到END
,它将超出范围。 可以在不同的作用域中声明具有相同名称的两个或多个变量,因为变量仅在自己的作用域中有效。 但是,在不同范围内声明具有相同名称的变量不是很好的编程习惯。
以@
符号开头的变量是会话变量。直到会话结束前它可用和可访问。
在本教程中,我们向您展示了如何在存储过程中声明变量,并讨论了变量的范围(作用域)。
五. 存储过程参数
MySQL存储过程参数示例
- IN参数示例
以下示例说明如何使用GetOfficeByCountry
存储过程中的IN参数来查询选择位于特定国家/地区的办公室。
USE `yiibaidb`;
DROP procedure IF EXISTS `GetOfficeByCountry`;
DELIMITER $$
USE `yiibaidb`$$
CREATE PROCEDURE GetOfficeByCountry(IN countryName VARCHAR(255))
BEGIN
SELECT *
FROM offices
WHERE country = countryName;
END$$
DELIMITER ;
countryName
是存储过程的IN
参数。在存储过程中,我们查询位于countryName
参数指定的国家/地区的所有办公室。
注:它的变量是直接利用参数名调用的,这也就是为什么参数名不能和字段名一样的原因。
假设我们想要查询在美国(USA)的所有办事处,我们只需要将一个值(USA)传递给存储过程,如下所示:
CALL GetOfficeByCountry('USA');
执行上面查询语句,得到以下结果
要在法国获得所有办事处,我们将France字符串传递给GetOfficeByCountry
存储过程,如下所示:
CALL GetOfficeByCountry('France')
- OUT参数示例
以下存储过程通过订单状态返回订单数量。它有两个参数:
orderStatus
:IN
参数,它是要对订单计数的订单状态。total
:存储指定订单状态的订单数量的OUT参数。
以下是CountOrderByStatus
存储过程的源代码。
USE `yiibaidb`;
DROP procedure IF EXISTS `CountOrderByStatus`;
DELIMITER $$
CREATE PROCEDURE CountOrderByStatus(
IN orderStatus VARCHAR(25),
OUT total INT)
BEGIN
SELECT count(orderNumber)
INTO total
FROM orders
WHERE status = orderStatus;
END$$
DELIMITER ;
要获取发货订单的数量,我们调用CountOrderByStatus
存储过程,并将订单状态传递为已发货,并传递参数(@total
)以获取返回值。
CALL CountOrderByStatus('Shipped',@total);
SELECT @total;
执行上面查询语句后,得到以下结果
+--------+
| @total |
+--------+
| 303 |
+--------+
1 row in set
- INOUT参数示例
以下示例演示如何在存储过程中使用INOUT参数。如下查询语句 -
DELIMITER $$
CREATE PROCEDURE set_counter(INOUT count INT(4),IN inc INT(4))
BEGIN
SET count = count + inc;
END$$
DELIMITER ;
上面查询语句是如何运行的?
set_counter
存储过程接受一个INOUT
参数(count
)和一个IN
参数(inc
)。在存储过程中,通过inc参数的值增加计数器(count
)。
下面来看看如何调用set_counter存储过程:
SET @counter = 1;
CALL set_counter(@counter,1); -- 2
CALL set_counter(@counter,1); -- 3
CALL set_counter(@counter,5); -- 8
SELECT @counter; -- 8
六. 返回多个值的存储过程示例
我们来看看示例数据库(yiibaidb)中的orders
表。
mysql> desc orders;
+----------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| orderNumber | int(11) | NO | PRI | NULL | |
| orderDate | date | NO | | NULL | |
| requiredDate | date | NO | | NULL | |
| shippedDate | date | YES | | NULL | |
| status | varchar(15) | NO | | NULL | |
| comments | text | YES | | NULL | |
| customerNumber | int(11) | NO | MUL | NULL | |
+----------------+-------------+------+-----+---------+-------+
7 rows in set
以下存储过程接受客户编号,并返回发货(shipped
),取消(canceled
),解决(resolved
)和争议(disputed
)的订单总数。
DELIMITER $$
CREATE PROCEDURE get_order_by_cust(
IN cust_no INT,
OUT shipped INT,
OUT canceled INT,
OUT resolved INT,
OUT disputed INT)
BEGIN
-- shipped
SELECT
count(*) INTO shipped
FROM
orders
WHERE
customerNumber = cust_no
AND status = 'Shipped';
-- canceled
SELECT
count(*) INTO canceled
FROM
orders
WHERE
customerNumber = cust_no
AND status = 'Canceled';
-- resolved
SELECT
count(*) INTO resolved
FROM
orders
WHERE
customerNumber = cust_no
AND status = 'Resolved';
-- disputed
SELECT
count(*) INTO disputed
FROM
orders
WHERE
customerNumber = cust_no
AND status = 'Disputed';
END$$
除IN
参数之外,存储过程还需要4个额外的OUT
参数:shipped
, canceled
, resolved
和 disputed
。 在存储过程中,使用带有COUNT
函数的SELECT
语句根据订单状态获取相应的订单总数,并将其分配给相应的参数。
要使用get_order_by_cust
存储过程,可以传递客户编号和四个用户定义的变量来获取输出值。
执行存储过程后,使用SELECT
语句输出变量值。
+----------+-----------+-----------+-----------+
| @shipped | @canceled | @resolved | @disputed |
+----------+-----------+-----------+-----------+
| 22 | 0 | 1 | 1 |
+----------+-----------+-----------+-----------+
1 row in set
从PHP调用返回多个值的存储过程
以下代码片段显示如何从PHP程序中调用返回多个值的存储过程。
<?php
/**
* Call stored procedure that return multiple values
* @param $customerNumber
*/
function call_sp($customerNumber)
{
try {
$pdo = new PDO("mysql:host=localhost;dbname=yiibaidb", 'root', '123456');
// execute the stored procedure
$sql = 'CALL get_order_by_cust(:no,@shipped,@canceled,@resolved,@disputed)';
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':no', $customerNumber, PDO::PARAM_INT);
$stmt->execute();
$stmt->closeCursor();
// execute the second query to get values from OUT parameter
$r = $pdo->query("SELECT @shipped,@canceled,@resolved,@disputed")
->fetch(PDO::FETCH_ASSOC);
if ($r) {
printf('Shipped: %d, Canceled: %d, Resolved: %d, Disputed: %d',
$r['@shipped'],
$r['@canceled'],
$r['@resolved'],
$r['@disputed']);
}
} catch (PDOException $pe) {
die("Error occurred:" . $pe->getMessage());
}
}
call_sp(141);
在@符号之前的用户定义的变量与数据库连接相关联,因此它们可用于在调用之间进行访问。
七. 循环语句
MySQL提供循环语句,允许您根据条件重复执行一个SQL代码块。 MySQL中有三个循环语句:WHILE
,REPEAT
和LOOP
。
我们将在以下部分中更详细地检查每个循环语句。
WHILE循环
WHILE
语句的语法如下:
WHILE expression DO
statements
END WHILE
WHILE
循环在每次迭代开始时检查表达式。 如果expressione
为TRUE
,MySQL将执行WHILE
和END WHILE
之间的语句,直到expression
为FALSE
。 WHILE循环称为预先测试条件循环,因为它总是在执行前检查语句的表达式。
下面的流程图说明了WHILE
循环语句:
以下是在存储过程中使用WHILE
循环语句的示例:
DELIMITER $$
DROP PROCEDURE IF EXISTS test_mysql_while_loop$$
CREATE PROCEDURE test_mysql_while_loop()
BEGIN
DECLARE x INT;
DECLARE str VARCHAR(255);
SET x = 1;
SET str = '';
WHILE x <= 5 DO
SET str = CONCAT(str,x,',');
SET x = x + 1;
END WHILE;
SELECT str;
END$$
DELIMITER ;
在上面的test_mysql_while_loop
存储过程中:
首先,重复构建str
字符串,直到x
变量的值大于5
。然后,使用SELECT
语句显示最终的字符串。要注意,如果不初始化x
变量的值,那么它默认值为NULL
。 因此,WHILE
循环语句中的条件始终为TRUE
,并且您将有一个不确定的循环,这是不可预料的。
下面来测试test_mysql_while_loopstored调用存储过程:
CALL test_mysql_while_loop();
执行上面查询语句,得到以下结果 -
mysql> CALL test_mysql_while_loop();
+------------+
| str |
+------------+
| 1,2,3,4,5, |
+------------+
1 row in set
Query OK, 0 rows affected
REPEAT循环
REPEAT
循环语句的语法如下:
REPEAT
statements;
UNTIL expression
END REPEAT
首先,MySQL
执行语句,然后评估求值表达式(expression
)。如果表达式(expression
)的计算结果为FALSE
,则MySQL
将重复执行该语句,直到该表达式计算结果为TRUE
。
因为REPEAT循环语句在执行语句后检查表达式(expression),因此REPEAT
循环语句也称为测试后循环。
下面的流程图说明了REPEAT循环语句的执行过程:
我们可以使用REPEAT
循环语句重写test_mysql_while_loop
存储过程,使用WHILE
循环语句:
DELIMITER $$
DROP PROCEDURE IF EXISTS mysql_test_repeat_loop$$
CREATE PROCEDURE mysql_test_repeat_loop()
BEGIN
DECLARE x INT;
DECLARE str VARCHAR(255);
SET x = 1;
SET str = '';
REPEAT
SET str = CONCAT(str,x,',');
SET x = x + 1;
UNTIL x > 5
END REPEAT;
SELECT str;
END$$
DELIMITER ;
注:要注意的是UNTIL表达式中没有分号;
。
执行上面查询语句,得到以下结果 -
mysql> CALL mysql_test_repeat_loop();
+------------+
| str |
+------------+
| 1,2,3,4,5, |
+------------+
1 row in set
Query OK, 0 rows affected
LOOP,LEAVE和ITERATE语句
有两个语句允许您用于控制循环:
LEAVE
语句用于立即退出循环,而无需等待检查条件。LEAVE
语句的工作原理就类似PHP,C/C++,Java等其他语言的break
语句一样。ITERATE
语句允许您跳过剩下的整个代码并开始新的迭代。ITERATE
语句类似于PHP,C/C++,Java等中的continue
语句。
MySQL还有一个LOOP语句,它可以反复执行一个代码块,另外还有一个使用循环标签的灵活性。
以下是使用LOOP循环语句的示例。
CREATE PROCEDURE test_mysql_loop()
BEGIN
DECLARE x INT;
DECLARE str VARCHAR(255);
SET x = 1;
SET str = '';
loop_label:
LOOP
IF x > 10 THEN
LEAVE loop_label;
END IF;
SET x = x + 1;
IF (x mod 2) THEN
ITERATE loop_label;
ELSE
SET str = CONCAT(str,x,',');
END IF;
END LOOP;
SELECT str;
END;
以上存储过程仅构造具有偶数字符串的字符串,例如2,4,6等。在LOOP
语句之前放置一个loop_label
循环标签。
- 如果
x
的值大于10
,则由于LEAVE
语句,循环被终止。 - 如果x的值是一个奇数,
ITERATE
语句忽略它下面的所有内容,并开始一个新的迭代。 - 如果x的值是偶数,则
ELSE
语句中的块将使用偶数构建字符串。
执行上面查询语句,得到以下结果 -
mysql> CALL test_mysql_loop();
+-------------+
| str |
+-------------+
| 2,4,6,8,10, |
+-------------+
1 row in set
Query OK, 0 rows affected
八.MySQL游标简介
要处理存储过程中的结果集,请使用游标。游标允许您迭代查询返回的一组行,并相应地处理每行。
MySQL游标为只读,不可滚动和敏感。
- 只读:无法通过光标更新基础表中的数据。
- 不可滚动:只能按照SELECT语句确定的顺序获取行。不能以相反的顺序获取行。 此外,不能跳过行或跳转到结果集中的特定行。
- 敏感:有两种游标, 敏感游标和不敏感游标。敏感游标指向实际数据,不敏感游标使用数据的临时副本。敏感游标比一个不敏感的游标执行得更快,因为它不需要临时拷贝数据。但是,对其他连接的数据所做的任何更改都将影响由敏感游标使用的数据,因此,如果不更新敏感游标所使用的数据,则更安全。 MySQL游标是敏感的。
您可以在存储过程,存储函数和触发器中使用MySQL游标。
使用MySQL游标
首先,必须使用DECLARE
语句声明游标:
DECLARE cursor_name CURSOR FOR SELECT_statement;
游标声明必须在变量声明之后。如果在变量声明之前声明游标,MySQL将会发出一个错误。游标必须始终与SELECT
语句相关联。
接下来,使用OPEN
语句打开游标。OPEN
语句初始化游标的结果集,因此您必须在从结果集中提取行之前调用OPEN
语句。
OPEN cursor_name;
然后,使用FETCH
语句来检索光标指向的下一行,并将光标移动到结果集中的下一行。
FETCH cursor_name INTO variables list;
当光标不再使用时,应该关闭它。
之后,可以检查是否有任何行记录可用,然后再提取它。最后,调用CLOSE
语句来停用光标并释放与之关联的内存,如下所示:
CLOSE cursor_name;
当使用MySQL游标时,还必须声明一个NOT FOUND
处理程序来处理当游标找不到任何行时的情况。 因为每次调用FETCH语句
时,游标会尝试读取结果集中的下一行。 当光标到达结果集的末尾时,它将无法获得数据,并且会产生一个条件。 处理程序用于处理这种情况。
要声明一个NOT FOUND
处理程序,参考以下语法:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
finished
是一个变量,指示光标到达结果集的结尾。请注意,处理程序声明必须出现在存储过程中的变量和游标声明之后。
下图说明了MySQL游标如何工作。
MySQL游标示例
为了更好地演示,我们将开发一个存储过程,来获取MySQL示例数据库(yiibaidb)中employees
表中所有员工的电子邮件列表。
首先,声明一些变量,一个用于循环员工电子邮件的游标和一个NOT FOUND
处理程序:
DECLARE finished INTEGER DEFAULT 0;
DECLARE email varchar(255) DEFAULT "";
-- declare cursor for employee email
DEClARE email_cursor CURSOR FOR
SELECT email FROM employees;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
接下来,使用OPEN
语句打开email_cursor
:
OPEN email_cursor;
然后,迭代电子邮件列表,并使用分隔符(;)连接每个电子邮件:
get_email: LOOP
FETCH email_cursor INTO v_email;
IF v_finished = 1 THEN
LEAVE get_email;
END IF;
-- build email list
SET email_list = CONCAT(v_email,";",email_list);
END LOOP get_email;
注:单层循环END LOOP后面的标签名可加可不加,如果嵌套循环,则必须加。
之后,在循环中,使用v_finished变量来检查列表中是否有任何电子邮件来终止循环。
最后,使用CLOSE
语句关闭游标:
CLOSE email_cursor;
build_email_list
存储过程所有代码如下:
DELIMITER $$
CREATE PROCEDURE build_email_list (INOUT email_list varchar(4000))
BEGIN
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE v_email varchar(100) DEFAULT "";
-- declare cursor for employee email
DEClARE email_cursor CURSOR FOR
SELECT email FROM employees;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN email_cursor;
get_email: LOOP
FETCH email_cursor INTO v_email;
IF v_finished = 1 THEN
LEAVE get_email;
END IF;
-- build email list
SET email_list = CONCAT(v_email,";",email_list);
END LOOP get_email;
CLOSE email_cursor;
END$$
DELIMITER ;
可以使用以下脚本测试build_email_list
存储过程:
SET @email_list = "";
CALL build_email_list(@email_list);
SELECT @email_list;
注:由于内容比较长,这里就不放上输出结果了。
九. 显示储存过程
显示存储过程字符
要显示存储过程的字符,请使用SHOW PROCEDURE STATUS
语句如下:
SHOW PROCEDURE STATUS [LIKE 'pattern' | WHERE expr];
SHOW PROCEDURE STATUS
语句是对SQL标准的MySQL扩展。此语句提供存储过程的字符,包括数据库,存储过程名称,类型,创建者等。可以使用LIKE
或WHERE
子句根据各种标准过滤出存储过程。
要列出您有权访问的数据库的所有存储过程,请使用SHOW PROCEDURE STATUS
语句,如下所示:
SHOW PROCEDURE STATUS;
如果要在特定数据库中显示存储过程,可以在SHOW PROCEDURE STATUS
语句中使用WHERE
子句:
SHOW PROCEDURE STATUS WHERE db = 'yiibaidb';
如果要显示具有特定模式的存储过程,例如,名称包含product
字符,则可以使用LIKE
操作符,如以下命令:
SHOW PROCEDURE STATUS WHERE name LIKE '%product%';
显示存储过程的源代码
要显示特定存储过程的源代码,请使用SHOW CREATE PROCEDURE
语句如下:
SHOW CREATE PROCEDURE stored_procedure_name
在SHOW CREATE PROCEDURE
关键字之后指定存储过程的名称。例如,要显示GetAllProducts
存储过程的代码,请使用以下语句:
SHOW CREATE PROCEDURE GetAllProducts;