乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

流程控制

导学

存储过程和函数需要条件判断和循环来封装复杂逻辑。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_idemp_namedeptscore
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_namescoregrade
大翔100优秀
白歌NULL未评分

结果解读

  • IF ... THEN 判断条件,条件为真执行对应分支
  • ELSEIF 用于多分支判断,从上至下依次匹配
  • ELSE 捕获所有不满足前面条件的情况
  • END IF 必须存在,标记 IF 块结束
  • 函数将分数转换为等级:90分以上优秀,60-89及格,60以下不及格,NULL为未评分

CASE 多分支

完整示例:使用 CASE 判断等级

当前数据状态

基于 employees 表:

emp_idemp_namedeptscore
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_namescorelevel
大翔100A
白歌NULLN

结果解读

  • CASE WHEN ... THEN ... END CASE 实现多分支匹配
  • 与 IF 类似,从上至下匹配第一个满足的条件
  • CASE 在表达式中使用时更直观,尤其是等值匹配场景

完整示例:CASE 等值匹配

当前数据状态

基于 employees 表:

emp_idemp_namedeptscore
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_namedeptdept_code
大翔技术部TECH
白歌技术部TECH

结果解读

  • CASE 表达式 WHEN 值 THEN ... 是等值匹配语法
  • 将 dept 字段的值与 WHEN 后的值逐一比较
  • 适合分支条件为固定值匹配的场景,代码更简洁

循环语句

WHILE(先判断,可能一次不执行)

完整示例:使用 WHILE 循环生成序号

当前数据状态

基于 employees 表:

emp_idemp_namedeptscore
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_idemp_namedeptscore
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_resultrepeat_result
010
  • WHILE 因为 i=10 不满足 i<5,所以 while_result 保持 0
  • REPEAT 先执行一次,将 10 加入 repeat_result,然后判断 10 < 5 为假,退出循环

LOOP(需配合 LEAVE/ITERATE)

完整示例:使用 LOOP 配合 LEAVE 和 ITERATE

当前数据状态

基于 employees 表:

emp_idemp_namedeptscore
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 表为空:

idemp_namescore
(空)(空)(空)

操作语句

创建批量插入的存储过程:

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

调用后:

idemp_namescore
1小崔80
2黄俪90

结果解读

  • 循环从 i=1 到 i=2,每次插入一条记录
  • IF(i=1, '小崔', '黄俪') 动态生成员工名称
  • 70 + i * 10 动态生成成绩:80, 90
  • 循环结束后表中共插入 2 条记录

常见误区

误区正解
IF 不需要 END IFMySQL 的 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)实现灵活循环

下一章引子:流程控制配合变量可以处理标量逻辑,但遍历查询结果集需要另一种机制——游标。

上一页
变量
下一页
游标