循环与游标
常规循环
MySQL里面有两种循环是跟其他语言类似的,都是根据某个条件来判断继续循环的
while循环
while 条件 DO
循环体;
end while;
while循环之前已经用过了,是最贴合一个常规语言的循环方式
repeat循环
repeat
循环体
until 条件 end repeat;
repeat循环是先循环再判断条件,类似于其他语言中的do while循环
特有循环
MySQL中的循环有时候会令人难以理解
loop循环
loop_name:loop
if 条件 THEN
满足条件时离开循环
leave loop_name;
end if;
end loop;
loop循环需要有个loop_name,这个其实是之前提过的label标记,也就是说在循环内部可以在某个条件满足时跳出label,结束循环。loop跟C语言中的循环很像,在正常使用的时候一般while循环就够了,loop循环大部分时候是和游标一起搭配使用的
loop循环和游标
SQL的执行不同于大部分编程语言最大的一点就是列执行能力,不同于普通的编程语言要单条循环,对于多条记录的更新,SQL可以直接写扁平的语句一次更新。如果实在逻辑太复杂,才会用到游标
游标就像个指针,可以依次指向select出来的每条记录进行处理,在指向的过程中就需要用到循环来控制进度,最常搭配的是loop循环
declare 游标名称 CURSOR for
select出的结果;
open 游标名称;
fetch 游标名称 into 变量;
执行业务逻辑
close 游标名称;
游标本身是上面这段处理逻辑,但是运行一次fetch只能获取到一条记录,需要一个循环来将指针往后移,因此加入loop之后的结构是
declare 游标名称 CURSOR for
select出的结果;
open 游标名称;
loop_name:loop
fetch 游标名称 into 变量;
if 条件 THEN
满足条件时离开循环
leave loop_name;
end if;
执行业务逻辑
end loop;
close 游标名称;
注意,最狗血的地方来了,游标没有提供一个优雅的条件来结束循环,因此结束循环的条件这里是最confusing的
这里结束的方法用了一个类似于exception的not found报错
declare found boolean default true;
declare CONTINUE HANDLER for not found set found = false;
当not found发生时,found变量就会本设置为false,因此完整的写法
declare found boolean default true;
declare CONTINUE HANDLER for not found set found = false;
declare 游标名称 CURSOR for
select出的结果;
open 游标名称;
loop_name:loop
set found = true;
fetch 游标名称 into 变量;
if not found THEN
满足条件时离开循环
leave loop_name;
end if;
执行业务逻辑
end loop;
close 游标名称;
声明好found变量后,当每次fetch 游标名称 into 变量运行完后,检查found的值,如果它变成false了,说明游标已经到了结尾没有值了,结束循环!
注意每次循环里面先set found = true; 这是因为不止fetch不到能触发not found,如果我们在业务逻辑里面用了一个select没有结果的话,也会触发not found, 如果不每次循环都设置一下就可能导致循环提前结束!
练习
假设一家公司里层级森严,每个人都有层级level,每个人都由另一个人领导。数据表如下
| user_id | full_name | manager_id | level |
|---|---|---|---|
| 1 | 老板 | 0 | 1 |
| 2 | 张三 | 1 | 2 |
| 3 | 李四 | 2 | 3 |
| 4 | 王五 | 2 | 3 |
user_id是主键,manager_id记录的是领导的user_id,level是层级
在表中,老板的manager_id是0,代表没有人管理老板,level是1最高。张三是老板的直接下属
现在有个需求,当员工提交某些材料需要审批的时候,要根据业务的不同审批到不同层级的领导。比如说员工只提交了100块的报销只需要level 2领导审批即可,员工要是提交了10000块的报销就得老板亲自审批。编写一个存储过程,入参是当前提交员工的user_id和需要level几的领导审批,输出这个领导的是谁(user_id)
CREATE FUNCTION `look_for_manager`(p_user_id int, p_level int) RETURNS bigint(20)
READS SQL DATA
BEGIN
declare v_user_id bigint;
declare v_manager_id bigint;
declare v_count int;
declare v_level int;
declare found boolean default false;
DECLARE CONTINUE HANDLER FOR NOT FOUND set found = false;
set v_count = 0;
set v_user_id = p_user_id;
LOOP
set found = true;
select manager_id, level into v_manager_id,v_level
from staff_table
where user_id = v_user_id;
if not found THEN
return null; #如果没有找到的话就返回null
end if;
if v_level = p_level THEN
return v_user_id; #满足匹配条件,返回找到的user_id
end if;
if v_count > 255 then
return null; #设置个最大循环次数,以免出错
end if;
set v_user_id = v_manager_id; #上面三个if都不匹配,说明还需要进行下一次循环查找,把user_id设置成当前的领导,再循环
set v_count = v_count + 1;
end loop;
END