二八佳人体似酥,腰悬利剑斩愚夫,虽然不见人头落,暗里教君骨髓枯。
上一章简单介绍了MySQL变量(二十七) ,如果没有看过,请观看上一章
一. 定义条件与处理程序
定义条件
是事先定义程序执行过程中可能遇到的问题,处理程序
定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。
说明:定义条件和处理程序在存储过程、存储函数中都是支持的。
一.一 存储过程报错便退出
创建一个存储过程,刚开始报错 user 表里面没有 email 字段
DELIMITER //CREATE PROCEDURE UpdateDataNoCondition()BEGINSET @x = 1;UPDATE user SET email = NULL WHERE id = '1';SET @x = 2;UPDATE user SET name = 'yjl' WHERE id = '1';SET @x = 3;END //DELIMITER ;
调用存储过程:
-- 1054 Unknown column 'email' in 'field list'
call UpdateDataNoCondition();-- 为1
select @x;
可以看到,此时@x变量的值为1。结合创建存储过程的SQL语句代码可以得出:在存储过程中未定义条件和处理程序,
且当存储过程中执行的SQL语句报错时,MySQL数据库会抛出错误,并退出当前SQL逻辑,不再向下继续执行。
一.二 定义条件
定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个错误名字
和指定的错误条件
关联起来。这个名字可以随后被用在定义处理程序的DECLARE HANDLER
语句中。
定义条件使用DECLARE语句,语法格式如下:
DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
错误码的说明:
MySQL_error_code
和sqlstate_value
都可以表示MySQL的错误。- MySQL_error_code是数值类型错误代码。
- sqlstate_value是长度为5的字符串类型错误代码。
- 例如,在ERROR 1418 (HY000)中,1418是MySQL_error_code,'HY000’是sqlstate_value。
- 例如,在ERROR 1142(42000)中,1142是MySQL_error_code,'42000’是sqlstate_value。
**举例1:**定义“Field_Not_Be_NULL”错误名与MySQL中违反非空约束的错误类型是“ERROR 1048 (23000)”对应。
#使用MySQL_error_code
DECLARE Field_Not_Be_NULL CONDITION FOR 1048;#使用sqlstate_value
DECLARE Field_Not_Be_NULL CONDITION FOR SQLSTATE '23000';
**举例2:**定义"ERROR 1148(42000)"错误,名称为command_not_allowed。
#使用MySQL_error_code
DECLARE command_not_allowed CONDITION FOR 1148;#使用sqlstate_value
DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';
一.三 定义处理程序
可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。定义处理程序时,使用DECLARE语句的语法如下:
DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
- 处理方式:处理方式有3个取值:CONTINUE、EXIT、UNDO。
CONTINUE
:表示遇到错误不处理,继续执行。EXIT
:表示遇到错误马上退出。UNDO
:表示遇到错误后撤回之前的操作。MySQL中暂时不支持这样的操作。
- 错误类型(即条件)可以有如下取值:
SQLSTATE '字符串错误码'
:表示长度为5的sqlstate_value类型的错误代码;MySQL_error_code
:匹配数值类型错误代码;错误名称
:表示DECLARE … CONDITION定义的错误条件名称。SQLWARNING
:匹配所有以01开头的SQLSTATE错误代码;NOT FOUND
:匹配所有以02开头的SQLSTATE错误代码;SQLEXCEPTION
:匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码;
- 处理语句:如果出现上述条件之一,则采用对应的处理方式,并执行指定的处理语句。语句可以是像“
SET 变量 = 值
”这样的简单语句,也可以是使用BEGIN ... END
编写的复合语句。
定义处理程序的几种方式,代码如下:
#方法1:捕获sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info = 'NO_SUCH_TABLE';#方法2:捕获mysql_error_value
DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';#方法3:先定义条件,再调用
DECLARE no_such_table CONDITION FOR 1146;
DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';#方法4:使用SQLWARNING
DECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';#方法5:使用NOT FOUND
DECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';#方法6:使用SQLEXCEPTION
DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';
一.四 定义条件和定义程序整合
一.四.一 对错误码进行处理程序
在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为1048时,执行CONTINUE操作,并且将@proc_value的值设置为-1。
DELIMITER //CREATE PROCEDURE UpdateDataNoCondition2()BEGIN#定义处理程序DECLARE CONTINUE HANDLER FOR 1054 SET @proc_value = -1;SET @x = 1;UPDATE user SET email = NULL WHERE id = '1';SET @x = 2;UPDATE user SET name = 'yjl' WHERE id = '1';SET @x = 3;END //DELIMITER ;
进行调用存储过程:
-- 变 OK 了, 之前错误码是 1054
call UpdateDataNoCondition2();-- 3 -1
select @x,@proc_value;
一.四.二 对异常值进行处理信息
给用户表 user 添加 name 的唯一约束
ALTER TABLE user ADD CONSTRAINT uk_user_1 UNIQUE(name);
插入一条数据, 执行两次时,第二次就会报错, key 为 1062
insert into user(name,sex,age) values('岳泽霖','男',18);
定义存储过程,创建是成功的
DELIMITER //CREATE PROCEDURE UpdateDataNoCondition3()BEGIN#定义处理程序DECLARE duplicate_entry CONDITION FOR SQLSTATE '23000' ;DECLARE EXIT HANDLER FOR duplicate_entry SET @proc_value = -1;SET @x = 1;insert into user(name,sex,age) values('abc','男',18);SET @x = 2;-- 第二回肯定报错的insert into user(name,sex,age) values('abc','男',18);SET @x = 3;END //DELIMITER ;
调用并查询:
-- 变 OK 了, 之前错误码是 1062 对应的是 23000
call UpdateDataNoCondition3();-- 2 -1
select @x,@proc_value;
二. 流程控制
解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:
顺序结构
:程序从上往下依次执行分支结构
:程序按条件进行选择执行,从两条或多条路径中选择一条执行循环结构
:程序满足一定条件下,重复执行一组语句
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。
条件判断语句
:IF 语句和 CASE 语句循环语句
:LOOP、WHILE 和 REPEAT 语句跳转语句
:ITERATE 和 LEAVE 语句
一直定义这三条数据进行演示和处理, 每一次语句之前都进行重置。
二.一 分支结构 IF
IF 语句的语法结构是:
IF 表达式1 THEN 操作1
[ELSEIF 表达式2 THEN 操作2]……
[ELSE 操作N]
END IF
根据表达式的结果为TRUE或FALSE执行相应的语句。这里“[]”中的内容是可选的。
- 特点:① 不同的表达式对应不同的操作 ② 使用在begin end中
年龄小于18时,修改姓名:
DELIMITER //
CREATEPROCEDURE `demo`.`p1`(IN `s_id` INT)-- 存储过程体BEGIN-- 查询年龄declare s_age int;set s_age = 0;select age into s_age from user where id = s_id;if s_age < 18 thenupdate user set name ='未成年' where id = s_id ;end if ;END //
DELIMITER ;
年龄小于18为未成年, 其余为成年
DELIMITER //
CREATEPROCEDURE `demo`.`p2`(IN `s_id` INT)-- 存储过程体BEGIN-- 查询年龄declare s_age int;set s_age = 0;select age into s_age from user where id = s_id;if s_age < 18 thenupdate user set name ='未成年' where id = s_id ;else update user set name ='成年' where id = s_id ;end if ;END //
DELIMITER ;
小于 18 为未成年, 为 18时为刚成年, 大于 18为成年
DELIMITER //
CREATEPROCEDURE `demo`.`p3`(IN `s_id` INT)-- 存储过程体BEGIN-- 查询年龄declare s_age int;set s_age = 0;select age into s_age from user where id = s_id;if s_age < 18 thenupdate user set name ='未成年' where id = s_id ;elseif s_age = 18 then update user set name ='刚成年' where id = s_id ;else update user set name ='成年' where id = s_id ;end if ;END //
DELIMITER ;
二.二 分支结构 CASE
CASE 语句的语法结构1:
#情况一:类似于switch
CASE 表达式
WHEN 值1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 值2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
CASE 语句的语法结构2:
#情况二:类似于多重if
CASE
WHEN 条件1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 条件2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
case when 形式:
DELIMITER //
CREATEPROCEDURE `demo`.`p4`(IN `s_id` INT)-- 存储过程体BEGIN-- 查询年龄declare s_age int;set s_age = 0;select age into s_age from user where id = s_id;case when s_age < 18 thenupdate user set name ='未成年' where id = s_id ;when s_age = 18 then update user set name ='刚成年' where id = s_id ;else update user set name ='成年' where id = s_id ;end case ;END //
DELIMITER ;
case 表达式 形式:
DELIMITER //
CREATEPROCEDURE `demo`.`p5`(IN `s_id` INT)-- 存储过程体BEGIN-- 查询年龄declare s_age int;set s_age = 0;select age into s_age from user where id = s_id;case s_agewhen 16 thenupdate user set name ='16 未成年' where id = s_id ;when 18 then update user set name ='18 刚成年' where id = s_id ;else update user set name ='成年' where id = s_id ;end case ;END //
DELIMITER ;
二.三 循环 LOOP
LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子句),跳出循环过程。
LOOP语句的基本格式如下:
[loop_label:] LOOP循环执行的语句
END LOOP [loop_label]
其中,loop_label表示LOOP语句的标注名称,该参数可以省略。
查询最小的年龄 循环相加 10 次之后的值
DELIMITER //
CREATEPROCEDURE `demo`.`p7`(OUT sum_age int)-- 存储过程体BEGIN-- 查询年龄declare s_age int;declare num1 int default 0;set s_age = 0;set sum_age = 0;select min(age) into s_age from user ;add_loop: LOOPset num1 = num1 +1;if num1 > 10 then leave add_loop;else set sum_age = sum_age + s_age;end if;end LOOP add_loop;END //
DELIMITER ;
调用:
set @sum_age = 0;
call p7(@sum_age);-- 160
select @sum_age;
二.四 循环结构 WHILE
WHILE语句创建一个带条件判断的循环过程。WHILE在执行语句执行时,先对指定的表达式进行判断,如果为真,就执行循环内的语句,否则退出循环。WHILE语句的基本格式如下:
[while_label:] WHILE 循环条件 DO循环体
END WHILE [while_label];
while_label为WHILE语句的标注名称;如果循环条件结果为真,WHILE语句内的语句或语句群被执行,直至循环条件为假,退出循环。
DELIMITER //
CREATEPROCEDURE `demo`.`p8`(OUT sum_age int)-- 存储过程体BEGIN-- 查询年龄declare s_age int;declare num1 int default 0;set s_age = 0;set sum_age = 0;select min(age) into s_age from user ;-- 循环处理while num1 < 10 DOset sum_age = sum_age + s_age;set num1 = num1 +1;end WHILE;END //
DELIMITER ;
调用:
set @sum_age = 0;
call p8(@sum_age);-- 160
select @sum_age;
二.五 循环结构 REPEAT
REPEAT语句创建一个带条件判断的循环过程。与WHILE循环不同的是,REPEAT 循环首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,
如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止。
REPEAT语句的基本格式如下:
[repeat_label:] REPEAT循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]
repeat_label为REPEAT语句的标注名称,该参数可以省略;REPEAT语句内的语句或语句群被重复,直至expr_condition为真。
DELIMITER //
CREATEPROCEDURE `demo`.`p9`(OUT sum_age int)-- 存储过程体BEGIN-- 查询年龄declare s_age int;declare num1 int default 0;set s_age = 0;set sum_age = 0;select min(age) into s_age from user ;-- 循环处理 REPEAT set sum_age = sum_age + s_age;set num1 = num1 +1;until num1 >= 10end REPEAT;END //
DELIMITER ;
调用:
set @sum_age = 0;
call p9(@sum_age);-- 160
select @sum_age;
对比三种循环结构:
1、这三种循环都可以省略名称,但如果循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名称。
2、
LOOP:一般用于实现简单的"死"循环
WHILE:先判断后执行
REPEAT:先执行后判断,无条件至少执行一次
二.六 跳转语句 LEAVE
LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出程序体的操作。
如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 break。
基本格式如下:
LEAVE 标记名
其中,label参数表示循环的标志。LEAVE和BEGIN … END或循环一起被使用。
统计执行的次数, num1 最多执行10次就退出
DELIMITER //
CREATEPROCEDURE `demo`.`p10`(OUT sum_age int ,OUT num1 int)-- 存储过程体BEGIN-- 查询年龄declare s_age int;set s_age = 0;set sum_age = 0;set num1 = 0;select min(age) into s_age from user ;-- 循环处理A: while num1 < 10 DOif s_age < 20 then set num1 = num1 +1;LEAVE A;else set sum_age = sum_age + s_age;set num1 = num1 +1;end if;end WHILE;END //
DELIMITER ;
调用:
set @sum_age = 0;
set @num1 = 0;
call p10(@sum_age,@num1);-- 0 1
select @sum_age, @num1;
二.七 跳转语句 ITERATE
ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序转到语句段开头处。如果你有面向过程的编程语言的使用经验,你可以把 ITERATE 理解为 continue,意思为“再次循环”。
语句基本格式如下:
ITERATE label
label参数表示循环的标志。ITERATE语句必须跟在循环标志前面。
DELIMITER //
CREATEPROCEDURE `demo`.`p11`(OUT sum_age int ,OUT num1 int)-- 存储过程体BEGIN-- 查询年龄declare s_age int;set s_age = 0;set sum_age = 0;set num1 = 0;select min(age) into s_age from user ;-- 循环处理A: while num1 < 10 DOif s_age < 20 then set num1 = num1 +1;iterate A;else set sum_age = sum_age + s_age;set num1 = num1 +1;end if;end WHILE;END //
DELIMITER ;
调用:
set @sum_age = 0;
set @num1 = 0;
call p11(@sum_age,@num1);-- 0 10
select @sum_age, @num1;
谢谢!!!