流程控制
导学
存储过程和函数需要条件判断和循环来封装复杂逻辑。MySQL 提供了 IF、CASE、LOOP、WHILE、REPEAT 等流程控制语句,与常规编程语言的逻辑结构类似。
定义
流程控制语句:用于控制 SQL 执行流程的语句,包括条件判断(IF、CASE)和循环(LOOP、WHILE、REPEAT)。
IF 条件判断
完整示例:使用 IF 判断成绩等级
当前数据状态
首先建立示例表并插入数据:
CREATE DATABASE IF NOT EXISTS school;
USE school;
DROP TABLE IF EXISTS employees;
CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20),
dept VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);
当前 employees 表数据:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用 IF 的存储函数:
DELIMITER //
CREATE FUNCTION GetGrade(score DECIMAL(5,2))
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
IF score IS NULL THEN
RETURN '未评分';
ELSEIF score >= 90 THEN
RETURN '优秀';
ELSEIF score >= 60 THEN
RETURN '及格';
ELSE
RETURN '不及格';
END IF;
END //
DELIMITER ;
-- 使用函数查询每个员工的绩效等级
SELECT emp_name, score, GetGrade(score) AS grade FROM employees;
操作后的结果
返回:
| emp_name | score | grade |
|---|---|---|
| 大翔 | 100 | 优秀 |
| 白歌 | NULL | 未评分 |
结果解读
IF ... THEN判断条件,条件为真执行对应分支ELSEIF用于多分支判断,从上至下依次匹配ELSE捕获所有不满足前面条件的情况END IF必须存在,标记 IF 块结束- 函数将分数转换为等级:90分以上优秀,60-89及格,60以下不及格,NULL为未评分
CASE 多分支
完整示例:使用 CASE 判断等级
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用 CASE 的存储函数:
DELIMITER //
CREATE FUNCTION GetLevel(score DECIMAL(5,2))
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
CASE
WHEN score IS NULL THEN RETURN 'N';
WHEN score >= 90 THEN RETURN 'A';
WHEN score >= 60 THEN RETURN 'B';
ELSE RETURN 'C';
END CASE;
END //
DELIMITER ;
-- 使用
SELECT emp_name, score, GetLevel(score) AS level FROM employees;
操作后的结果
返回:
| emp_name | score | level |
|---|---|---|
| 大翔 | 100 | A |
| 白歌 | NULL | N |
结果解读
CASE WHEN ... THEN ... END CASE实现多分支匹配- 与
IF类似,从上至下匹配第一个满足的条件 CASE在表达式中使用时更直观,尤其是等值匹配场景
完整示例:CASE 等值匹配
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用 CASE 等值匹配的存储函数:
DELIMITER //
CREATE FUNCTION GetDeptCode(dept VARCHAR(20))
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
CASE dept
WHEN '技术部' THEN RETURN 'TECH';
WHEN '市场部' THEN RETURN 'MKT';
ELSE RETURN 'OTHER';
END CASE;
END //
DELIMITER ;
-- 使用
SELECT emp_name, dept, GetDeptCode(dept) AS dept_code FROM employees;
操作后的结果
返回:
| emp_name | dept | dept_code |
|---|---|---|
| 大翔 | 技术部 | TECH |
| 白歌 | 技术部 | TECH |
结果解读
CASE 表达式 WHEN 值 THEN ...是等值匹配语法- 将
dept字段的值与WHEN后的值逐一比较 - 适合分支条件为固定值匹配的场景,代码更简洁
循环语句
WHILE(先判断,可能一次不执行)
完整示例:使用 WHILE 循环生成序号
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用 WHILE 的存储过程:
DELIMITER //
CREATE PROCEDURE PrintNumbers()
BEGIN
DECLARE i INT DEFAULT 1;
CREATE TEMPORARY TABLE IF NOT EXISTS temp_numbers (num INT);
TRUNCATE TABLE temp_numbers;
WHILE i <= 5 DO
INSERT INTO temp_numbers VALUES (i);
SET i = i + 1;
END WHILE;
SELECT * FROM temp_numbers;
DROP TEMPORARY TABLE IF EXISTS temp_numbers;
END //
DELIMITER ;
-- 调用
CALL PrintNumbers();
操作后的结果
返回:
| num |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
结果解读
WHILE 条件 DO ... END WHILE先判断条件,条件为真才执行循环体- 如果初始条件就不满足,循环体一次都不会执行
SET i = i + 1;是循环变量更新,缺少会导致死循环
REPEAT(先执行,至少执行一次)
完整示例:使用 REPEAT 循环累加
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用 REPEAT 的存储过程:
DELIMITER //
CREATE PROCEDURE CalcSumWithRepeat()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE total INT DEFAULT 0;
REPEAT
SET total = total + i;
SET i = i + 1;
UNTIL i > 5 END REPEAT;
SELECT total AS sum_1_to_5;
END //
DELIMITER ;
-- 调用
CALL CalcSumWithRepeat();
操作后的结果
返回:
| sum_1_to_5 |
|---|
| 15 |
结果解读
REPEAT ... UNTIL 条件 END REPEAT先执行循环体,再判断条件- 即使条件一开始就不满足,循环体也至少执行一次
- 本例计算 1+2+3+4+5 = 15
对比 WHILE 和 REPEAT 的差异:
DELIMITER //
CREATE PROCEDURE CompareWhileRepeat()
BEGIN
DECLARE i INT DEFAULT 10;
DECLARE while_result INT DEFAULT 0;
DECLARE repeat_result INT DEFAULT 0;
-- WHILE:条件不满足,一次都不执行
WHILE i < 5 DO
SET while_result = while_result + i;
SET i = i + 1;
END WHILE;
SET i = 10;
-- REPEAT:先执行一次,再判断条件
REPEAT
SET repeat_result = repeat_result + i;
SET i = i + 1;
UNTIL i < 5 END REPEAT;
SELECT while_result AS while_result, repeat_result AS repeat_result;
END //
DELIMITER ;
CALL CompareWhileRepeat();
返回:
| while_result | repeat_result |
|---|---|
| 0 | 10 |
WHILE因为i=10不满足i<5,所以while_result保持 0REPEAT先执行一次,将 10 加入repeat_result,然后判断10 < 5为假,退出循环
LOOP(需配合 LEAVE/ITERATE)
完整示例:使用 LOOP 配合 LEAVE 和 ITERATE
当前数据状态
基于 employees 表:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
操作语句
创建使用 LOOP 的存储过程:
DELIMITER //
CREATE PROCEDURE PrintWithLoop()
BEGIN
DECLARE i INT DEFAULT 1;
CREATE TEMPORARY TABLE IF NOT EXISTS temp_loop_result (num INT);
TRUNCATE TABLE temp_loop_result;
my_loop: LOOP
IF i > 5 THEN
LEAVE my_loop; -- 跳出循环,相当于 break
END IF;
IF i = 3 THEN
SET i = i + 1;
ITERATE my_loop; -- 跳过当前迭代,相当于 continue
END IF;
INSERT INTO temp_loop_result VALUES (i);
SET i = i + 1;
END LOOP my_loop;
SELECT * FROM temp_loop_result;
DROP TEMPORARY TABLE IF EXISTS temp_loop_result;
END //
DELIMITER ;
-- 调用
CALL PrintWithLoop();
操作后的结果
返回:
| num |
|---|
| 1 |
| 2 |
| 4 |
| 5 |
结果解读
LOOP本身没有退出条件,必须通过LEAVE标签 跳出LEAVE my_loop相当于其他语言的break,完全退出循环ITERATE my_loop相当于其他语言的continue,跳过本次循环剩余部分,进入下一次迭代- 本例中当
i = 3时,ITERATE跳过了插入操作,因此结果中没有 3
完整示例:使用循环批量插入数据
当前数据状态
建立临时表:
DROP TABLE IF EXISTS temp_employees;
CREATE TABLE temp_employees (
id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20),
score DECIMAL(5,2)
);
当前 temp_employees 表为空:
| id | emp_name | score |
|---|---|---|
| (空) | (空) | (空) |
操作语句
创建批量插入的存储过程:
DELIMITER //
CREATE PROCEDURE BatchInsertEmployees()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 2 DO
INSERT INTO temp_employees (emp_name, score)
VALUES (IF(i=1, '小崔', '黄俪'), 70 + i * 10);
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
-- 调用前确认表为空
SELECT COUNT(*) AS before_count FROM temp_employees;
-- 执行批量插入
CALL BatchInsertEmployees();
-- 调用后查看数据
SELECT * FROM temp_employees;
操作后的结果
调用前:
| before_count |
|---|
| 0 |
调用后:
| id | emp_name | score |
|---|---|---|
| 1 | 小崔 | 80 |
| 2 | 黄俪 | 90 |
结果解读
- 循环从
i=1到i=2,每次插入一条记录 IF(i=1, '小崔', '黄俪')动态生成员工名称70 + i * 10动态生成成绩:80, 90- 循环结束后表中共插入 2 条记录
常见误区
| 误区 | 正解 |
|---|---|
IF 不需要 END IF | MySQL 的 IF 块必须以 END IF 结束。 |
LEAVE 可以用在任意地方 | LEAVE 只能用于跳出带有标签的 BEGIN...END 或循环。 |
ITERATE 和 LEAVE 一样 | ITERATE 是跳过本次继续下一次(continue);LEAVE 是完全跳出循环(break)。 |
面试考点
Q:WHILE 和 REPEAT 的区别?
WHILE先判断条件再执行循环体,可能一次都不执行;REPEAT先执行循环体再判断条件,至少执行一次。
Q:LEAVE 和 ITERATE 的区别?
LEAVE相当于break,完全跳出循环;ITERATE相当于continue,跳过本次循环的剩余部分,进入下一次迭代。
Q:MySQL 存储过程中有 FOR 循环吗?
MySQL 5.7 没有类似其他语言的
FOR i IN 1..10语法。需要用WHILE或REPEAT配合计数器变量实现。游标(CURSOR)可以遍历结果集,详见《游标》文档。
小结
IF...ELSEIF...ELSE...END IF实现条件分支CASE...WHEN...THEN...END CASE实现多分支匹配WHILE先判断后循环,REPEAT先循环后判断LOOP配合LEAVE(break)和ITERATE(continue)实现灵活循环
下一章引子:流程控制配合变量可以处理标量逻辑,但遍历查询结果集需要另一种机制——游标。