存储过程 - 流程控制、游标与条件处理
流程控制(IF / CASE / WHILE / REPEAT / LOOP)让存储过程能分支与循环;游标用来逐行处理查询结果,**条件处理程序(Handler)**用来处理游标或异常等情况。
一、IF 条件判断
IF 条件1 THEN
...
ELSEIF 条件2 THEN -- 可选
...
ELSE -- 可选
...
END IF;
示例:根据局部变量 score 判定等级(≥85 优秀;≥60 且 <85 及格;<60 不及格):
CREATE PROCEDURE p3()
BEGIN
DECLARE score INT DEFAULT 85;
DECLARE score_txt VARCHAR(10);
IF score >= 85 THEN
SET score_txt := '优秀';
ELSEIF score >= 60 THEN
SET score_txt := '及格';
ELSE
SET score_txt := '不及格';
END IF;
SELECT score_txt;
END;
CALL p3();
二、CASE 多分支
语法一:等值匹配
CASE case_value
WHEN when_value1 THEN statement_list1
[WHEN when_value2 THEN statement_list2] ...
[ELSE statement_list]
END CASE;
语法二:任意条件匹配
CASE
WHEN search_condition1 THEN statement_list1
[WHEN search_condition2 THEN statement_list2] ...
[ELSE statement_list]
END CASE;
示例:根据传入的月份(IN)判定季度(OUT),使用语法一:
CREATE PROCEDURE p6(IN month INT, OUT season VARCHAR(10))
BEGIN
CASE
WHEN month IN (1, 2, 3) THEN SET season := '第一季度';
WHEN month IN (4, 5, 6) THEN SET season := '第二季度';
WHEN month IN (7, 8, 9) THEN SET season := '第三季度';
WHEN month IN (10, 11, 12) THEN SET season := '第四季度';
ELSE SET season := '非法参数';
END CASE;
END;
CALL p6(4, @season);
SELECT @season; -- 第二季度
上例
CASE后没有写case_value,属于语法二的写法(以WHEN 条件判断)。若写成CASE month WHEN ...,则为语法一。
三、循环
3.1 WHILE:先判断,后执行
WHILE 条件 DO
-- SQL 逻辑
END WHILE;
-- 计算从 1 累加到 n
CREATE PROCEDURE p7(IN n INT)
BEGIN
DECLARE total INT DEFAULT 0;
WHILE n > 0 DO
SET total := total + n;
SET n := n - 1;
END WHILE;
SELECT total;
END;
CALL p7(10); -- 55
3.2 REPEAT:先执行,后判断
REPEAT
-- SQL 逻辑
UNTIL 条件
END REPEAT;
注意:
UNTIL的条件为真时才退出,与WHILE的条件相反。
CREATE PROCEDURE p8(IN n INT)
BEGIN
DECLARE total INT DEFAULT 0;
REPEAT
SET total := total + n;
SET n := n - 1;
UNTIL n <= 0
END REPEAT;
SELECT total;
END;
CALL p8(10000); -- 50005000
3.3 LOOP:配合 LEAVE / ITERATE
LOOP 本身是死循环,需要配合两个语句控制:
| 语句 | 作用 |
|---|---|
LEAVE 标签; | 退出指定标记的循环(类似 break) |
ITERATE 标签; | 跳过本次循环剩下的语句,直接进入下一次循环(类似 continue),只能用在循环中 |
[begin_label:] LOOP
-- SQL 逻辑
END LOOP [end_label];
-- 计算从 1 累加到 n
CREATE PROCEDURE p9(IN n INT)
BEGIN
DECLARE total INT DEFAULT 0;
sum: LOOP
IF n <= 0 THEN
LEAVE sum;
END IF;
SET total := total + n;
SET n := n - 1;
END LOOP sum;
SELECT total;
END;
CALL p9(100); -- 5050
-- 计算从 1 到 n 之间的偶数累加(奇数直接跳过)
CREATE PROCEDURE p10(IN n INT)
BEGIN
DECLARE total INT DEFAULT 0;
sum: LOOP
IF n <= 0 THEN
LEAVE sum;
ELSEIF n % 2 != 0 THEN
SET n := n - 1;
ITERATE sum;
END IF;
SET total := total + n;
SET n := n - 1;
END LOOP sum;
SELECT total;
END;
CALL p10(10); -- 30(10+8+6+4+2)
四、游标(CURSOR)
游标用来存储查询结果集,在存储过程 / 函数中可以对结果集逐行循环处理。使用流程:声明 → 打开 → 获取 → 关闭。
| 步骤 | 语法 |
|---|---|
| 声明游标 | DECLARE 游标名称 CURSOR FOR 查询语句; |
| 打开游标 | OPEN 游标名称; |
| 获取记录 | FETCH 游标名称 INTO 变量 [, 变量] ...; |
| 关闭游标 | CLOSE 游标名称; |
示例:查询 tb_user 中年龄小于等于 uage 的用户的姓名(name)和专业(profession),插入新表 tb_user_pro(id, name, profession):
CREATE PROCEDURE p11(IN uage INT)
BEGIN
DECLARE uname VARCHAR(100);
DECLARE upro VARCHAR(100);
DECLARE u_cursor CURSOR FOR SELECT name, profession FROM tb_user WHERE age <= uage;
DROP TABLE IF EXISTS tb_user_pro;
CREATE TABLE IF NOT EXISTS tb_user_pro(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
profession VARCHAR(100)
);
OPEN u_cursor;
WHILE true DO
FETCH u_cursor INTO uname, upro;
INSERT INTO tb_user_pro VALUES (NULL, uname, upro);
END WHILE;
CLOSE u_cursor;
END;
执行
CALL p11(40);时,游标取完最后一行后再FETCH会产生02000(NOT FOUND)警告,程序会因此报No data - zero rows fetched...并中断,下一节的 Handler 就是用来处理它的。
五、条件处理程序(HANDLER)
条件处理程序用于定义流程控制中遇到问题时的处理步骤。
DECLARE handler_action HANDLER FOR condition_value [, condition_value] ... statement;
| 项 | 取值 | 含义 |
|---|---|---|
handler_action | CONTINUE | 继续执行当前程序 |
EXIT | 终止执行当前程序(所在的 BEGIN ... END 块) | |
condition_value | SQLSTATE 'sqlstate_value' | 指定状态码,如 '02000' |
SQLWARNING | 所有以 01 开头的 SQLSTATE 的简写 | |
NOT FOUND | 所有以 02 开头的 SQLSTATE 的简写(游标无数据) | |
SQLEXCEPTION | 所有未被 SQLWARNING 或 NOT FOUND 捕获的 SQLSTATE 的简写 |
示例:在 p11 中声明 EXIT Handler,游标无数据时关闭游标并退出:
CREATE PROCEDURE p11(IN uage INT)
BEGIN
DECLARE uname VARCHAR(100);
DECLARE upro VARCHAR(100);
DECLARE u_cursor CURSOR FOR SELECT name, profession FROM tb_user WHERE age <= uage;
DECLARE EXIT HANDLER FOR SQLSTATE '02000' CLOSE u_cursor;
DROP TABLE IF EXISTS tb_user_pro;
CREATE TABLE IF NOT EXISTS tb_user_pro(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
profession VARCHAR(100)
);
OPEN u_cursor;
WHILE true DO
FETCH u_cursor INTO uname, upro;
INSERT INTO tb_user_pro VALUES (NULL, uname, upro);
END WHILE;
CLOSE u_cursor;
END;
CALL p11(30);
也可以写成
DECLARE EXIT HANDLER FOR NOT FOUND CLOSE u_cursor;,效果相同。
顺序易错:存储过程块内的声明必须按 变量 → 游标 → Handler 的顺序写在最前面,否则会报语法错误。
速查
-- IF:多分支
IF score >= 85 THEN SET r := '优秀';
ELSEIF score >= 60 THEN SET r := '及格';
ELSE SET r := '不及格';
END IF;
-- WHILE:先判断再执行(条件为真才进入)
WHILE n > 0 DO SET n := n - 1; END WHILE;
-- REPEAT:先执行再判断(UNTIL 条件为真时退出)
REPEAT SET n := n - 1; UNTIL n <= 0 END REPEAT;
-- LOOP:LEAVE 退出,ITERATE 跳过本次
sum: LOOP
IF n <= 0 THEN LEAVE sum; END IF;
SET n := n - 1;
END LOOP sum;
-- 游标:声明 → 打开 → 取值 → 关闭(需配合 Handler 处理 02000)
DECLARE u_cursor CURSOR FOR SELECT name FROM tb_user;
DECLARE EXIT HANDLER FOR SQLSTATE '02000' CLOSE u_cursor;
OPEN u_cursor;
FETCH u_cursor INTO uname;
CLOSE u_cursor;
| 要做什么 | 用什么 |
|---|---|
| 分支 | IF;等值或区间多选用 CASE |
| 循环 | WHILE 先判断,REPEAT 先执行 |
| 跳出 / 跳过 | LEAVE(退出)、ITERATE(下一次) |
| 逐行处理结果 | 游标 + EXIT HANDLER FOR SQLSTATE '02000' |
相关
- 存储过程:创建、调用、参数(IN / OUT / INOUT)
- 存储过程:变量:局部变量的
DECLARE与赋值 - SQL DQL 数据查询语言:游标所包装的
SELECT查询