二八佳人体似酥,腰悬利剑斩愚夫,虽然不见人头落,暗里教君骨髓枯。

上一章简单介绍了MySQL变量(二十七) ,如果没有看过,请观看上一章

一. 定义条件与处理程序

定义条件是事先定义程序执行过程中可能遇到的问题,处理程序定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。

说明:定义条件和处理程序在存储过程、存储函数中都是支持的。

一.一 存储过程报错便退出

创建一个存储过程,刚开始报错 user 表里面没有 email 字段

DELIMITER //

CREATE PROCEDURE UpdateDataNoCondition()
	BEGIN
		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 ;

调用存储过程:

--  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_codesqlstate_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);

MySQL流程控制(二十八)_条件与程序

定义存储过程,创建是成功的

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 语句

一直定义这三条数据进行演示和处理, 每一次语句之前都进行重置。

MySQL流程控制(二十八)_条件与程序_02

二.一 分支结构 IF

IF 语句的语法结构是:

IF 表达式1 THEN 操作1
[ELSEIF 表达式2 THEN 操作2]……
[ELSE 操作N]
END IF

根据表达式的结果为TRUE或FALSE执行相应的语句。这里“[]”中的内容是可选的。

  • 特点:① 不同的表达式对应不同的操作 ② 使用在begin end中

年龄小于18时,修改姓名:

DELIMITER //
CREATE
    PROCEDURE `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 then
				update user set name ='未成年' where id = s_id ;
		 end if ;
		 
	END //
DELIMITER ;

MySQL流程控制(二十八)_存储过程_03

年龄小于18为未成年, 其余为成年

DELIMITER //
CREATE
    PROCEDURE `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 then
				update user set name ='未成年' where id = s_id ;
		 else 
				update user set name ='成年' where id = s_id ;
		 end if ;
		 
	END //
DELIMITER ;

MySQL流程控制(二十八)_存储过程_04

小于 18 为未成年, 为 18时为刚成年, 大于 18为成年

DELIMITER //
CREATE
    PROCEDURE `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 then
				update 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 ;

MySQL流程控制(二十八)_存储过程_05

二.二 分支结构 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 //
CREATE
    PROCEDURE `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 then
				update 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 ;

MySQL流程控制(二十八)_存储过程_06

case 表达式 形式:

DELIMITER //
CREATE
    PROCEDURE `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_age
		  when  16 then
				update 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 ;

MySQL流程控制(二十八)_mysql_07

二.三 循环 LOOP

LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子句),跳出循环过程。

LOOP语句的基本格式如下:

[loop_label:] LOOP
	循环执行的语句
END LOOP [loop_label]

其中,loop_label表示LOOP语句的标注名称,该参数可以省略。

查询最小的年龄 循环相加 10 次之后的值

DELIMITER //
CREATE
    PROCEDURE `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: LOOP
			 set 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 //
CREATE
    PROCEDURE `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 DO
			  set 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 //
CREATE
    PROCEDURE `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 >= 10
		 end 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 //
CREATE
    PROCEDURE `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 DO
		    if 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 //
CREATE
    PROCEDURE `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 DO
		    if 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;




谢谢!!!