


专栏:MySQL数据库成长记
个人主页:手握风云
目录
[一、SQL 编程](#一、SQL 编程)
[1.1. CASE](#1.1. CASE)
[1. 语法1](#1. 语法1)
[2. 语法2](#2. 语法2)
[3. 示例](#3. 示例)
[1.2. WHILE 循环](#1.2. WHILE 循环)
[1. 语法](#1. 语法)
[2. 示例](#2. 示例)
[1.3. REPEAT 循环](#1.3. REPEAT 循环)
[1. 语法](#1. 语法)
[2. 示例](#2. 示例)
[1.4. LOOP 循环](#1.4. LOOP 循环)
[1. 语法](#1. 语法)
[2. 示例](#2. 示例)
[1.5. 游标](#1.5. 游标)
[1. 语法](#1. 语法)
[2. 示例](#2. 示例)
[1.6. 条件处理程序](#1.6. 条件处理程序)
[1. 定义](#1. 定义)
[2. 示例](#2. 示例)
一、SQL 编程
1.1. CASE
1. 语法1
- CASE 后的 case_value是一个表达式,该表达式的值与每一个 WHEN 子句中的 when_value 比较,当找到一个相等的 when value 时,执行相应的 THEN 子句的 statement list 。
- 如果没有相等的 when value,则执行 ELSE 子句 statement list(如果存在ELSE)。
sql
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
2. 语法2
- 计算每个 WHEN 子句 search_condition 表达式,直到其中一个表达式为真,此时执行相应的 THEN 子句的 statement list。
- 如果 search_condition 都不相等,则执行 ELSE 子句 statement_list(如果存在ELSE)。
sql
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
注意:每个 statement_list 由一条或多条 SQL 语句组成且不允许为空。但可以使用 BEGIN... END; 块;如果没有 when_value 或 search _condition 与测试值匹配,并照 CASE 语句不包含 ELSE 子句,则会导致 CASE 语句错误。
3. 示例
传⼊⼀个状态码,输出该状态码表示的含义。0表示成功;10001表示⽤户名或密码错误;10002,您没有对应的权限,请联系管理员;20001,你传入的参数有误;20002,没有找到相应的结果。
sql
delimiter //
CREATE PROCEDURE p5 (IN code INT, OUT ret VARCHAR(50))
BEGIN
CASE code
WHEN 0 THEN
SET ret := '成功';
WHEN 10001 THEN
SET ret := '用户名或密码错误';
WHEN 10002 THEN
SET ret := '你没有对应的权限';
WHEN 20001 THEN
SET ret := '你传入的参数有误';
WHEN 20002 THEN
SET ret := '没有找到相应的结果';
ELSE
SET ret := '服务器错误,请联系管理员';
END CASE;
END//
delimiter ;
-- 调用
CALL p5(10001, @ret);
-- 查看结果
SELECT @ret;
根据传入的月份,输出该月份属于哪个季度。1~3月为第⼀季度;4~6月为第二季度;7~9月为第三季度;10~12月为第四季度。
sql
delimiter //
CREATE PROCEDURE p6(IN month INT, OUT ret VARCHAR(50))
BEGIN
CASE
WHEN month >= 1 AND month <= 3 THEN
SET ret := '第一季度';
WHEN month >= 4 AND month <= 6 THEN
SET ret := '第二季度';
WHEN month >= 7 AND month <= 9 THEN
SET ret := '第三季度';
WHEN month >= 10 AND month <= 12 THEN
SET ret := '第四季度';
ELSE
SET ret := '非法输入';
END CASE;
END//
delimiter ;
CALL p6(5, @ret);
SELECT @ret;
1.2. WHILE 循环
1. 语法
先判断条件表达式 search_condition 是否为 TRUE,如果条件成立,则执行循环体中的 statement list。
sql
WHILE search_condition DO
statement_list
END WHILE;
2. 示例
传入⼀个数 n,计算从1累加到 n 的值。
sql
delimiter //
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 //
delimiter;
CALL p7(100);
1.3. REPEAT 循环
1. 语法
- 先执行一次循环体中的 statement 1ist,再先判断条件表达式 search_condition 是否为 TRUE,如果条件成立,则继续执行循环体中的语句,如果条件不成立则退出循环。
- statement list 至少会执行一次,类似与 JAVA 中的do ... while 循环。
sql
REPEAT
statement_list
UNTIL search_condition
END REPEAT;
2. 示例
传入⼀个数n,计算从1累加到n的值。
sql
delimiter //
CREATE PROCEDURE p8 (IN n INT)
BEGIN
-- 定义一个变量保存结果
DECLARE
total INT DEFAULT 0;
-- 循环
REPEAT
SELECT
n;
SET total := total + n;
SET n := n - 1;
UNTIL n <= 0
END REPEAT;
-- 查看结果
SELECT
total;
END //
delimiter;
CALL p8 (10);
1.4. LOOP 循环
1. 语法
LOOP也可以实现一个简单的循环,并且当满足某个条件时终止当前循环或退出整个循环,通常配合以下两个子句使用。
- LEAVE label:退出整个循环,类似于 JAVA 中的 break;
- ITERATE label:终止当前循环,进入下一次循环,类似于 JAVA 中的 continue;
sql
[begin_label:] LOOP
statement_list
END LOOP [end_label]
2. 示例
sql
delimiter //
CREATE PROCEDURE p9 (IN n INT)
BEGIN
DECLARE
total INT DEFAULT 0;
sum_label :
LOOP
IF n <= 0 THEN
LEAVE sum_label;
END IF;
SET total := total + n;
SET n := n - 1;
END LOOP sum_label;
SELECT
total;
END //
delimiter;
CALL p9 (10);
1.5. 游标
MySQL 中的游标是⼀种数据库对象,允许在存储过程和函数中对查询到的结果集进⾏逐行检索。在结果集中,可以通过向前移动游标,实现每一行的数据读取。
sql
SELECT * from student;

注意:MySQL的游标是只读的,不能进行更新操作。
1. 语法
sql
-- 声明游标
DECLARE 游标名 CURSOR FOR 查询语句;
-- 打开游标
OPEN 游标名;
-- 获取游标记录
FETCH 游标名 INTO 变量[, 变量] ...;
-- 关闭游标
CLOSE 游标名;
2. 示例
传入班级编号,查询学生表中属于该班级的学生信息,并将符合条件的学生信息写⼊到⼀张 新表中。我们需要先定义用于接收查询结果集中每一行列值的变量,包括存储学生姓名的变量和存储班级名的变量;然后声明游标,该游标的查询语句关联 student 表与 class 表,根据传入的班级编号筛选出符合条件的学生姓名(student_name)和班级名(class_name),用于接收筛选后的查询结果集;接着创建新表 t_student_class,表结构包含 id(自增主键)、student_name(学生姓名)、class_name(班级名);下面再执行开启游标操作,使游标可以开始获取查询结果集中的记录,并将获取到的学生姓名和班级名插入到新创建的 t_student_class 表中;最后关闭游标。
sql
SELECT
s.`name` student_name,
c.`name` class_name
FROM
student s,
class c
WHERE
s.class_id = c.id
AND s.class_id = 1;

sql
delimiter //
CREATE PROCEDURE p10 (IN class_id INT)
BEGIN
-- 定义变量用于接收查询结果集中每一行中列的值
-- 学生姓名
DECLARE
student_name VARCHAR (20);
-- 班级名称
DECLARE
class_name VARCHAR (20);
-- 声明游标
DECLARE
s_cursor CURSOR FOR SELECT
s.`name` student_name,
c.`name` class_name
FROM
student s,
class c
WHERE
s.class_id = c.id
AND s.class_id = 1;
DROP TABLE
IF EXISTS t_student_class;
CREATE TABLE
IF NOT EXISTS t_student_class (id BIGINT PRIMARY KEY auto_increment, student_name VARCHAR (20) NOT NULL, class_name VARCHAR (20) NOT NULL);
-- 开启游标
OPEN s_cursor;
-- 遍历结果集
WHILE TRUE DO
-- 获取游标记录
FETCH s_cursor INTO student_name,
class_name;
-- 写入到新表
INSERT INTO t_student_class
VALUES
(NULL, student_name, class_name);
END WHILE;
-- 关闭游标
CLOSE s_cursor;
END //
delimiter;
1.6. 条件处理程序
按照上面的 SQL 语句,我们调用执行一次这个函数就会出现错误。这是因为 WHILE 循环没有一个有效的终止条件,导致发生了越界。

1. 定义
定义条件是事先定义程序执行过程中可能遇到的问题,处理程序定义了在遇到问题时应当采取的处理方式,使用条件处理程序保证存储过程或函数在遇到警告或错误时能继续执⾏,可以增强程序处理问题的 能⼒,避免程序异常停止运行。
sql
DECLARE handler_action HANDLER
FOR condition_value [, condition_value] ...
statement;
-- handler_action 的可选值:
-- CONTINUE -- 继续执行当前程序
-- EXIT -- 终止执行当前程序
-- condition_value 的可选值:
-- mysql_error_code -- MYSQL错误码
-- SQLSTATE [VALUE] sqlstate_value -- 状态码
-- SQLWARNING -- 所有以01开头的SQLSTATE代码
-- NOT FOUND -- 所有以02开头的SQLSTATE代码
-- SQLEXCEPTION -- 所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码
例如,游标越界出现的状态码:
sql
mysql> call p10(1);
ERROR 1329 (02000): No data - zero rows fetched, selected, or processed
游标必须在条件处理程序之前被声明,并且变量必须在游标或条件处理程序之前被声明。
2. 示例
加入条件处理程序,解决游标越界问题。
sql
delimiter //
CREATE PROCEDURE p11 (IN class_id INT)
BEGIN
-- 定义变量用于接收查询结果集中每一行中列的值
-- 学生姓名
DECLARE
student_name VARCHAR (20);
-- 班级名称
DECLARE
class_name VARCHAR (20);
DECLARE is_done bool DEFAULT FALSE;
-- 声明游标
DECLARE
s_cursor CURSOR FOR SELECT
s.`name` student_name,
c.`name` class_name
FROM
student s,
class c
WHERE
s.class_id = c.id
AND s.class_id = class_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET is_done := TRUE;
DROP TABLE
IF EXISTS t_student_class;
CREATE TABLE
IF NOT EXISTS t_student_class (id BIGINT PRIMARY KEY auto_increment, student_name VARCHAR (20) NOT NULL, class_name VARCHAR (20) NOT NULL);
-- 开启游标
OPEN s_cursor;
-- 遍历结果集
WHILE NOT is_done DO
-- 获取游标记录
FETCH s_cursor INTO student_name,
class_name;
-- 写入到新表
INSERT INTO t_student_class
VALUES
(NULL, student_name, class_name);
END WHILE;
-- 关闭游标
CLOSE s_cursor;
END //
delimiter ;
CALL p11(1);