07.循环与游标

459 阅读4分钟

循环与游标

常规循环

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