DROP procedure IF EXISTS `getGameName`;#删除储存过程
#注意参数名不能与字段名相同
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数据库引擎,使其更加灵活和强大。
除了这些优点之外,存储过程有其自身的缺点,在数据库中使用它们之前,您应该注意这些缺点。
内存,CPU开销大,复杂逻辑困难,不便调试,开发和维护不易
MySQL存储过程有自己的优点和缺点。开发应用程序时,您应该决定是否应该或不应该根据业务需求使用存储过程。
三. 创建储存过程,调用储存过程
#创建储存过程
DELIMITER //
CREATE PROCEDURE GetAllProducts()
BEGIN
SELECT * FROM products;
END //
DELIMITER ;
#调用储存过程
CALL STORED_PROCEDURE_NAME();
navicat客户端创建:
1. 打开数据库,左侧目录 -》函数-》右击新建函数
2. 设置参数,入参,出参,出入参
3. 编写sql语句,保存,创建完毕
四. 变量
要在存储过程中声明一个变量,可以使用DECLARE
语句,如下所示:
DECLARE variable_name datatype(size) DEFAULT default_value;
下面来更详细地解释上面的语句:
DECLARE
关键字后面要指定变量名。变量名必须遵循MySQL表列名称的命名规则。INT
,VARCHAR
,DATETIME
等。NULL
。但是可以使用DEFAULT
关键字为变量分配默认值。例如,可以声明一个名为total_sale
的变量,数据类型为INT
,默认值为0
,如下所示:
DECLARE total_sale INT DEFAULT 0;
MySQL允许您使用单个DECLARE
语句声明共享相同数据类型的两个或多个变量,如下所示:
DECLARE x, y INT DEFAULT 0;
我们声明了两个整数变量x
和y
,并将其默认值设置为0
。
当声明了一个变量后,就可以开始使用它了。要为变量分配一个值,可以使用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
,它将超出范围。 可以在不同的作用域中声明具有相同名称的两个或多个变量,因为变量仅在自己的作用域中有效。 但是,在不同范围内声明具有相同名称的变量不是很好的编程习惯。
以@
符号开头的变量是会话变量。直到会话结束前它可用和可访问。
在本教程中,我们向您展示了如何在存储过程中声明变量,并讨论了变量的范围(作用域)。
五. 存储过程参数
1.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')
2.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
要获取正在处理的订单数量,调用CountOrderByStatus
存储过程,如下所示:
执行上面查询语句后,得到以下结果 -
+------------------+
| total_in_process |
+------------------+
| 7 |
+------------------+
1 row in set
以下示例演示如何在存储过程中使用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程序中调用返回多个值的存储过程。
/**
* 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 expression DO
statements
END WHILE
WHILE
循环在每次迭代开始时检查表达式。 如果expressionevaluates
为TRUE
,MySQL将执行WHILE
和END WHILE
之间的语句,直到expressionevaluates
为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
。要注意,如果不初始化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
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
有两个语句允许您用于控制循环:
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游标为只读,不可滚动和敏感。
您可以在存储过程,存储函数和触发器中使用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示例数据库(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;
之后,在循环中,使用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;
参考:https://www.yiibai.com/mysql/stored-procedure.html