ToB企服应用市场:ToB评测及商务社交产业平台

标题: MySQL | 尚硅谷 | 第16章_变量、流程控制与游标 [打印本页]

作者: 前进之路    时间: 2024-12-12 02:29
标题: MySQL | 尚硅谷 | 第16章_变量、流程控制与游标
MySQL条记:第16章_变量、流程控制与游标


  
第16章_变量、流程控制与游标

1. 变量

在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。
在 MySQL 数据库中,变量分为 系统变量 以及 用户自定义变量 。
1.1 系统变量

1.1.1 系统变量分类

变量由系统定义,不是用户定义,属于 服务器 层面。启动MySQL服务,生成MySQL服务实例期间,MySQL将为MySQL服务器内存中的系统变量赋值,这些系统变量定义了当前MySQL服务实例的属性、特征。这些系统变量的值要么是 编译MySQL时参数 的默认值,要么是 设置文件 (比方my.ini等)中的参数值。各人可以通过网址 https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html 查看MySQL文档的系统变量。
系统变量分为全局系统变量(需要添加 global 关键字)以及会话系统变量(需要添加 session 关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。假如不写,默认会话级别。静态变量(在 MySQL 服务实例运行期间它们的值不能使用 set 动态修改)属于特别的全局系统变量。
每一个MySQL客户机成功连接MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制。如下图:


在MySQL中有些系统变量只能是全局的,比方 max_connections 用于限定服务器的最大连接数;有些系统变量作用域既可以是全局又可以是会话,比方 character_set_client 用于设置客户端的字符集;有些系统变量的作用域只能是当前会话,比方 pseudo_thread_id 用于标记当前会话的 MySQL 连接 ID。
1.1.2 查看系统变量

查看全部或部分系统变量
  1. #查看所有全局变量
  2. SHOW GLOBAL VARIABLES;
  3. #查看所有会话变量
  4. SHOW SESSION VARIABLES;
  5. #或
  6. SHOW VARIABLES;
  7. #查看满足条件的部分系统变量。
  8. SHOW GLOBAL VARIABLES LIKE '%标识符%';
  9. #查看满足条件的部分会话变量
  10. SHOW SESSION VARIABLES LIKE '%标识符%';
  11. 12345678910111213
复制代码
举例:
  1. SHOW GLOBAL VARIABLES LIKE 'admin_%';
  2. 1
复制代码

  1. #查看指定的系统变量的值
  2. SELECT @@global.变量名;
  3. #查看指定的会话变量的值
  4. SELECT @@session.变量名;
  5. #或者
  6. SELECT @@变量名;
  7. 1234567
复制代码

方式1:
修改MySQL 设置文件 ,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)
方式2:
在MySQL服务运行期间,使用“set”下令重新设置系统变量的值
  1. #为某个系统变量赋值
  2. #方式1:
  3. SET @@global.变量名=变量值;
  4. #方式2:
  5. SET GLOBAL 变量名=变量值;
  6. #为某个会话变量赋值
  7. #方式1:
  8. SET @@session.变量名=变量值;
  9. #方式2:
  10. SET SESSION 变量名=变量值;
  11. 12345678910111213
复制代码
举例:
  1. SELECT @@global.autocommit;
  2. SET GLOBAL autocommit=0;
  3. SELECT @@session.tx_isolation;
  4. SET @@session.tx_isolation='read-uncommitted';
  5. SET GLOBAL max_connections = 1000;
  6. SELECT @@global.max_connections;
  7. 1234567891011
复制代码
1.2 用户变量

1.2.1 用户变量分类

用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以 一个“@” 开头。根据作用范围差别,又分为会话用户变量 和局部变量
**会话用户变量**:
作用域和会话变量一样,只对当前连接 会话有效。
**局部变量**:
只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数 中使用。
1.2.2 会话用户变量

变量的定义
#方式1:“=”或“:=”
SET @用户变量 = 值;
SET @用户变量 := 值;
#方式2:“:=” 或 INTO关键字
  1. SELECT @用户变量 := 表达式 [FROM 等子句];
  2. SELECT 表达式 INTO @用户变量        [FROM 等子句];
  3. 12
复制代码
查看用户变量的值 (查看、比力、运算等)
  1. SELECT @用户变量
  2. 1
复制代码
举例
  1. SET @a = 1;
  2. SELECT @a;
  3. SELECT @num := COUNT(*) FROM employees;
  4. SELECT @num;
  5. SELECT AVG(salary) INTO @avgsalary FROM employees;
  6. SELECT @avgsalary;
  7. SELECT @big;       
  8. #查看某个未声明的变量时,将得到NULL值
  9. 123456789101112
复制代码
1.2.3 局部变量

**定义**:
可以使用 DECLARE 语句定义一个局部变量
**作用域:**
仅仅在定义它的 BEGIN … END 中有效
**位置:**
只能放在 BEGIN … END 中,而且只能放在第一句
  1. BEGIN
  2. #声明局部变量
  3. DECLARE 变量名1 变量数据类型 [DEFAULT 变量默认值];
  4. DECLARE 变量名2,变量名3,... 变量数据类型 [DEFAULT 变量默认值];
  5. #为局部变量赋值
  6. SET 变量名1 = 值;
  7. SELECT 值 INTO 变量名2 [FROM 子句];
  8. #查看局部变量的值
  9. SELECT 变量1,变量2,变量3;
  10. END
  11. 123456789101112
  12. 1.定义变量
  13. DECLARE 变量名 类型 [default 值];       
  14. # 如果没有DEFAULT子句,初始值为NULL
  15. 12
复制代码
举例:
  1. DECLARE        myparam        INT        DEFAULT 100;
  2. 1
  3. 2.变量赋值
复制代码
方式1:一般用于赋简朴的值
  1. SET 变量名=值;
  2. SET 变量名:=值;
  3. 12
复制代码
方式2:一般用于赋表中的字段值
  1. SELECT 字段名或表达式 INTO 变量名 FROM 表;
  2. 1
  3. **3.使用变量(查看、比较、运算等)**
  4. SELECT 局部变量名;
  5. 1
复制代码
举例1:声明局部变量,并分别赋值为employees表中employee_id为102的last_name和salary
  1. DELIMITER //
  2. CREATE PROCEDURE set_value()
  3. BEGIN
  4. DECLARE emp_name VARCHAR(25);
  5. DECLARE sal DOUBLE(10,2);
  6. SELECT last_name,salary INTO emp_name,sal
  7. FROM employees
  8. WHERE employee_id = 102;
  9. SELECT emp_name,sal;
  10. END //
  11. DELIMITER ;
  12. 1234567891011
复制代码
举例2:声明两个变量,求和并打印 (分别使用会话用户变量、局部变量的方式实现)
  1. #方式1:使用用户变量
  2. SET @m=1;
  3. SET @n=1;
  4. SET @sum=@m+@n;
  5. SELECT @sum;
  6. #方式2:使用局部变量
  7. DELIMITER //
  8. CREATE PROCEDURE add_value()
  9. BEGIN
  10. #局部变量
  11. DECLARE m INT DEFAULT 1;
  12. DECLARE n INT DEFAULT 3;
  13. DECLARE SUM INT;
  14. SET SUM = m+n;
  15. SELECT SUM;
  16. END //
  17. DELIMITER ;
  18. 12345678910111213141516171819
复制代码
举例3:创建存储过程“different_salary”查询某员工和他领导的薪资差距,并用IN参数emp_id接收员工id,用OUT参数dif_salary输出薪资差距结果。
  1. #声明
  2. DELIMITER //
  3. CREATE PROCEDURE different_salary(IN emp_id INT,OUT dif_salary DOUBLE)
  4. BEGIN
  5. #声明局部变量
  6. DECLARE emp_sal,mgr_sal DOUBLE DEFAULT 0.0;
  7. DECLARE mgr_id INT;
  8. SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  9. SELECT manager_id INTO mgr_id FROM employees WHERE employee_id = emp_id;
  10. SELECT salary INTO mgr_sal FROM employees WHERE employee_id = mgr_id;
  11. SET dif_salary = mgr_sal - emp_sal;
  12. END //
  13. DELIMITER ;
  14. #调用
  15. SET @emp_id = 102;
  16. CALL different_salary(@emp_id,@diff_sal);
  17. #查看
  18. SELECT @diff_sal;
  19. 1234567891011121314151617181920212223
复制代码
1.2.4 对比会话用户变量与局部变量

.作用域定义位置语法会话用户变量当前会话会话的任何地方加@符号,不用指定类型局部变量定义它的BEGIN END中BEGIN END的第一句话一般不用加@,需要指定类型 演示代码

  1. #1. 变量
  2. #1.1 变量: 系统变量(全局系统变量、会话系统变量)  vs 用户自定义变量
  3. #1.2 查看系统变量
  4. #查询全局系统变量
  5. SHOW GLOBAL VARIABLES; #617
  6. #查询会话系统变量
  7. SHOW SESSION VARIABLES; #640
  8. SHOW VARIABLES; #默认查询的是会话系统变量
  9. #查询部分系统变量
  10. SHOW GLOBAL VARIABLES LIKE 'admin_%';
  11. SHOW VARIABLES LIKE 'character_%';
  12. #1.3 查看指定系统变量
  13. SELECT @@global.max_connections;
  14. SELECT @@global.character_set_client;
  15. #错误:
  16. SELECT @@global.pseudo_thread_id;
  17. #错误:
  18. SELECT @@session.max_connections;
  19. SELECT @@session.character_set_client;
  20. SELECT @@session.pseudo_thread_id;
  21. SELECT @@character_set_client; #先查询会话系统变量,再查询全局系统变量
  22. #1.4 修改系统变量的值
  23. #全局系统变量:
  24. #方式1:
  25. SET @@global.max_connections = 161;
  26. #方式2:
  27. SET GLOBAL max_connections = 171;
  28. #针对于当前的数据库实例是有效的,一旦重启mysql服务,就失效了。
  29. #会话系统变量:
  30. #方式1:
  31. SET @@session.character_set_client = 'gbk';
  32. #方式2:
  33. SET SESSION character_set_client = 'gbk';
  34. #针对于当前会话是有效的,一旦结束会话,重新建立起新的会话,就失效了。
  35. #1.5 用户变量
  36. /*
  37. ① 用户变量 : 会话用户变量 vs 局部变量
  38. ② 会话用户变量:使用"@"开头,作用域为当前会话。
  39. ③ 局部变量:只能使用在存储过程和存储函数中的。
  40. */
  41. #1.6 会话用户变量
  42. /*
  43. ① 变量的声明和赋值:
  44. #方式1:“=”或“:=”
  45. SET @用户变量 = 值;
  46. SET @用户变量 := 值;
  47. #方式2:“:=” 或 INTO关键字
  48. SELECT @用户变量 := 表达式 [FROM 等子句];
  49. SELECT 表达式 INTO @用户变量  [FROM 等子句];
  50. ② 使用
  51. SELECT @变量名
  52. */
  53. #准备工作
  54. CREATE DATABASE dbtest16;
  55. USE dbtest16;
  56. CREATE TABLE employees
  57. AS
  58. SELECT * FROM atguigudb.`employees`;
  59. CREATE TABLE departments
  60. AS
  61. SELECT * FROM atguigudb.`departments`;
  62. SELECT * FROM employees;
  63. SELECT * FROM departments;
  64. #测试:
  65. #方式1:
  66. SET @m1 = 1;
  67. SET @m2 := 2;
  68. SET @sum := @m1 + @m2;
  69. SELECT @sum;
  70. /*输出
  71. +------+
  72. | @sum |
  73. +------+
  74. |    3 |
  75. +------+
  76. */
  77. #方式2:
  78. SELECT @count := COUNT(*) FROM employees;
  79. /*
  80. +--------------------+
  81. | @count := COUNT(*) |
  82. +--------------------+
  83. |                107 |
  84. +--------------------+
  85. */
  86. SELECT @count;
  87. /*输出
  88. +--------+
  89. | @count |
  90. +--------+
  91. |    107 |
  92. +--------+
  93. */
  94. SELECT AVG(salary) INTO @avg_sal FROM employees;
  95. SELECT @avg_sal;
  96. /*
  97. +-------------------+
  98. | @avg_sal          |
  99. +-------------------+
  100. | 6461.682242990654 |
  101. +-------------------
  102. */
  103. #1.7 局部变量
  104. /*
  105. 1、局部变量必须满足:
  106. ① 使用DECLARE声明
  107. ② 声明并使用在BEGIN ... END 中 (使用在存储过程、函数中)
  108. ③ DECLARE的方式声明的局部变量必须声明在BEGIN中的首行的位置。
  109. 2、声明格式:
  110. DECLARE 变量名 类型 [default 值];  # 如果没有DEFAULT子句,初始值为NULL
  111. 3、赋值:
  112. 方式1:
  113. SET 变量名=值;
  114. SET 变量名:=值;
  115. 方式2:
  116. SELECT 字段名或表达式 INTO 变量名 FROM 表;
  117. 4、使用
  118. SELECT 局部变量名;
  119. */
  120. #举例:
  121. DELIMITER //
  122. CREATE PROCEDURE test_var()
  123. BEGIN
  124.         #1、声明局部变量
  125.         DECLARE a INT DEFAULT 0;
  126.         DECLARE b INT ;
  127.         #DECLARE a,b INT DEFAULT 0;
  128.         DECLARE emp_name VARCHAR(25);
  129.        
  130.         #2、赋值
  131.         SET a = 1;
  132.         SET b := 2;
  133.        
  134.         SELECT last_name INTO emp_name FROM employees WHERE employee_id = 101;
  135.        
  136.         #3、使用
  137.         SELECT a,b,emp_name;       
  138. END //
  139. DELIMITER ;
  140. #调用存储过程
  141. CALL test_var();
  142. /*输出:
  143. +------+------+----------+
  144. | a    | b    | emp_name |
  145. +------+------+----------+
  146. |    1 |    2 | Kochhar  |
  147. +------+------+----------+
  148. */
  149. #举例1:声明局部变量,并分别赋值为employees表中employee_id为102的last_name和salary
  150. DELIMITER //
  151. CREATE PROCEDURE test_pro()
  152. BEGIN
  153.         #声明
  154.         DECLARE emp_name VARCHAR(25);
  155.         DECLARE sal DOUBLE(10,2) DEFAULT 0.0;
  156.         #赋值
  157.         SELECT last_name,salary INTO emp_name,sal
  158.         FROM employees
  159.         WHERE employee_id = 102;
  160.         #使用
  161.         SELECT emp_name,sal;
  162. END //
  163. DELIMITER ;
  164. #调用存储过程
  165. CALL test_pro();
  166. /*
  167. +----------+----------+
  168. | emp_name | sal      |
  169. +----------+----------+
  170. | De Haan  | 17000.00 |
  171. +----------+----------+
  172. */
  173. SELECT last_name,salary FROM employees
  174. WHERE employee_id = 102;
  175. #举例2:声明两个变量,求和并打印 (分别使用会话用户变量、局部变量的方式实现)
  176. #方式1:使用会话用户变量
  177. SET @v1 = 10;
  178. SET @v2 := 20;
  179. SET @result := @v1 + @v2;
  180. #查看
  181. SELECT @result;
  182. #方式2:使用局部变量
  183. DELIMITER //
  184. CREATE PROCEDURE add_value()
  185. BEGIN
  186.         #声明
  187.         DECLARE value1,value2,sum_val INT;
  188.        
  189.         #赋值
  190.         SET value1 = 10;
  191.         SET value2 := 100;
  192.        
  193.         SET sum_val = value1 + value2;
  194.         #使用
  195.         SELECT sum_val;
  196. END //
  197. DELIMITER ;
  198. #调用存储过程
  199. CALL add_value();
  200. /*
  201. +---------+
  202. | sum_val |
  203. +---------+
  204. |     110 |
  205. +---------+
  206. */
  207. #举例3:创建存储过程“different_salary”查询某员工和他领导的薪资差距,并用IN参数emp_id接收员工id,
  208. #用OUT参数dif_salary输出薪资差距结果。
  209. DELIMITER //
  210. CREATE PROCEDURE different_salary(IN emp_id INT,OUT dif_salary DOUBLE)
  211. BEGIN
  212.         #分析:查询出emp_id员工的工资;查询出emp_id员工的管理者的id;查询管理者id的工资;计算两个工资的差值
  213.        
  214.         #声明变量
  215.         DECLARE emp_sal DOUBLE DEFAULT 0.0; #记录员工的工资
  216.         DECLARE mgr_sal DOUBLE DEFAULT 0.0; #记录管理者的工资
  217.        
  218.         DECLARE mgr_id INT DEFAULT 0; #记录管理者的id
  219.        
  220.        
  221.         #赋值
  222.         SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  223.        
  224.         SELECT manager_id INTO mgr_id FROM employees WHERE employee_id = emp_id;
  225.         SELECT salary INTO mgr_sal FROM employees WHERE employee_id = mgr_id;
  226.        
  227.         SET dif_salary = mgr_sal - emp_sal;
  228. END //
  229. DELIMITER ;
  230. #调用存储过程
  231. SET @emp_id := 103;
  232. SET @dif_sal := 0;
  233. CALL different_salary(@emp_id,@dif_sal);
  234. SELECT @dif_sal;
  235. /*
  236. +----------+
  237. | @dif_sal |
  238. +----------+
  239. |     8000 |
  240. +----------+
  241. */
  242. SELECT * FROM employees;
  243. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
复制代码
2. 定义条件与处理程序

定义条件是事先定义程序实验过程中大概遇到的问题, 处理程序定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继承实验。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。
分析:定义条件和处理程序在存储过程、存储函数中都是支持的。
2.1 案例分析

案例分析:创建一个名称为“UpdateDataNoCondition”的存储过程。代码如下:
  1. DELIMITER //
  2. CREATE PROCEDURE UpdateDataNoCondition()
  3. BEGIN
  4. SET @x = 1;
  5. UPDATE employees SET email = NULL WHERE last_name = 'Abel';
  6. SET @x = 2;
  7. UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
  8. SET @x = 3;
  9. END //
  10. DELIMITER ;
复制代码
调用存储过程:
  1. CALL UpdateDataNoCondition();
  2. #ERROR 1048 (23000): Column 'email' cannot be null
  3. SELECT @x;
  4. /*
  5. +------+
  6. | @x        |
  7. +------+
  8. |        1        |
  9. +------+*/
复制代码
可以看到,此时@x变量的值为1。结合创建存储过程的SQL语句代码可以得出:在存储过程中未定义条件和处理程序,且当存储过程中实验的SQL语句报错时,MySQL数据库会抛出错误,并退出当前SQL逻辑,不再向下继承实验。
2.2 定义条件

定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清楚。它将一个 错误名字 和 指定的错误条件 关联起来。这个名字可以随后被用在定义处理程序的 DECLARE HANDLER 语句中。
定义条件使用DECLARE语句,语法格式如下:
  1. DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
  2. 1
复制代码
错误码的分析:

举例1:
定义“Field_Not_Be_NULL”错误名与MySQL中违反非空束缚的错误类型是“ERROR 1048 (23000)”对应。
#使用MySQL_error_code
  1. DECLARE Field_Not_Be_NULL CONDITION FOR 1048;
  2. #使用sqlstate_value
  3. DECLARE Field_Not_Be_NULL CONDITION FOR SQLSTATE '23000';
  4. 1234
复制代码
举例2:
定义"ERROR 1148(42000)"错误,名称为command_not_allowed。
  1. #使用MySQL_error_code
  2. DECLARE command_not_allowed CONDITION FOR 1148;
  3. #使用sqlstate_value
  4. DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';
  5. 12345
复制代码
2.3 定义处理程序

可以为SQL实验过程中发生的某种类型的错误定义特别的处理程序。定义处理程序时,使用DECLARE语句的语法如下:
  1. DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
  2. 1
  3. **处理方式:处理方式有3个取值:CONTINUE、EXIT、UNDO**
复制代码

  1. **错误类型(即条件)可以有如下取值:**
复制代码

**处理语句:**
假如出现上述条件之一,则接纳对应的处理方式,并实验指定的处理语句。语句可以是像“ SET 变量 = 值 ”这样的简朴语句,也可以是使用 BEGIN … END 编写的复合语句。
定义处理程序的几种方式,代码如下:
  1. #方法1:捕获sqlstate_value
  2. DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info = 'NO_SUCH_TABLE';
  3. #方法2:捕获mysql_error_value
  4. DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';
  5. #方法3:先定义条件,再调用
  6. DECLARE no_such_table CONDITION FOR 1146;
  7. DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';
  8. #方法4:使用SQLWARNING
  9. DECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';
  10. #方法5:使用NOT FOUND
  11. DECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';
  12. #方法6:使用SQLEXCEPTION
  13. DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';
  14. 123456789101112131415161718
复制代码
2.4 案例办理

在存储过程中,定义处理程序,捕捉sqlstate_value值,当遇到MySQL_error_code值为1048时,实验CONTINUE操作,并且将@proc_value的值设置为-1。
  1. DELIMITER //
  2. CREATE PROCEDURE UpdateDataNoCondition()
  3. BEGIN
  4. #定义处理程序
  5. DECLARE CONTINUE HANDLER FOR 1048 SET @proc_value = -1;
  6. SET @x = 1;
  7. UPDATE employees SET email = NULL WHERE last_name = 'Abel';
  8. SET @x = 2;
  9. UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
  10. SET @x = 3;
  11. END //
  12. DELIMITER ;
  13. 1234567891011121314
复制代码
调用过程:
  1. CALL UpdateDataWithCondition();
  2. #Query OK, 0 rows affected (0.01 sec)
  3. SELECT @x,@proc_value;
  4. /*
  5. +        ------        +-------------        +
  6. | @x        |        @proc_value |
  7. +        ------        +-------------        +
  8. |                3 |        -1  |
  9. +        ------        +-------------        +*/
  10. 123456789
复制代码
举例:
创建一个名称为“InsertDataWithCondition”的存储过程:
在存储过程中,定义处理程序,捕捉sqlstate_value值,当遇到sqlstate_value值为23000时,实验EXIT操作,并且将@proc_value的值设置为-1。
  1. #准备工作
  2. CREATE TABLE departments
  3. AS
  4. SELECT * FROM atguigudb.`departments`;
  5. ALTER TABLE departments
  6. ADD CONSTRAINT uk_dept_name UNIQUE(department_id);
  7. DELIMITER //
  8. CREATE PROCEDURE InsertDataWithCondition()
  9. BEGIN
  10. DECLARE duplicate_entry CONDITION FOR SQLSTATE '23000' ;
  11. DECLARE EXIT HANDLER FOR duplicate_entry SET @proc_value = -1;
  12. SET @x = 1;
  13. INSERT INTO departments(department_name) VALUES('测试');
  14. SET @x = 2;
  15. INSERT INTO departments(department_name) VALUES('测试');
  16. SET @x = 3;
  17. END //
  18. DELIMITER ;
  19. 1234567891011121314151617181920
复制代码
调用存储过程:
  1. CALL InsertDataWithCondition();
  2. #Query OK, 0 rows affected (0.01 sec)
  3. SELECT @x,@proc_value;
  4. /*
  5. +        ------        +-------------        +
  6. | @x        |        @proc_value |
  7. +        ------        +-------------        +
  8. |                2 |        -1  |
  9. +        ------        +-------------        +
  10. */
  11. 12345678910
复制代码
演示代码

  1. #2. 定义条件和处理程序
  2. #2.1 错误演示:
  3. #错误代码: 1364
  4. #Field 'email' doesn't have a default value
  5. INSERT INTO employees(last_name)
  6. VALUES('Tom');
  7. DESC employees;
  8. #错误演示:
  9. DELIMITER //
  10. CREATE PROCEDURE UpdateDataNoCondition()
  11.         BEGIN
  12.                 SET @x = 1;
  13.                 UPDATE employees SET email = NULL WHERE last_name = 'Abel';
  14.                 SET @x = 2;
  15.                 UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
  16.                 SET @x = 3;
  17.         END //
  18. DELIMITER ;
  19. #调用存储过程
  20. #错误代码: 1048
  21. #Column 'email' cannot be null
  22. CALL UpdateDataNoCondition();#ERROR 1048 (23000): Column 'email' cannot be null
  23. SELECT @x;
  24. #输出:@x = 1处出现错误
  25. /*
  26. +------+
  27. | @x   |
  28. +------+
  29. |    1 |
  30. +------+
  31. */
  32. #2.2 定义条件
  33. #格式:DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
  34. #举例1:定义“Field_Not_Be_NULL”错误名与MySQL中违反非空约束的错误类型
  35. #是“ERROR 1048 (23000)”对应。
  36. #方式1:使用MySQL_error_code
  37. DECLARE Field_Not_Be_NULL CONDITION FOR 1048;
  38. #方式2:使用sqlstate_value
  39. DECLARE Field_Not_Be_NULL CONDITION FOR SQLSTATE '23000';
  40. #举例2:定义"ERROR 1148(42000)"错误,名称为command_not_allowed。
  41. #方式1:使用MySQL_error_code
  42. DECLARE command_not_allowed CONDITION FOR 1148;
  43. #方式2:使用sqlstate_value
  44. DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';
  45. #2.3 定义处理程序
  46. #格式:DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
  47. #举例:
  48. #方法1:捕获sqlstate_value
  49. DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info = 'NO_SUCH_TABLE';
  50. #方法2:捕获mysql_error_value
  51. DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';
  52. #方法3:先定义条件,再调用
  53. DECLARE no_such_table CONDITION FOR 1146;
  54. DECLARE CONTINUE HANDLER FOR no_such_table SET @info = 'NO_SUCH_TABLE';
  55. #方法4:使用SQLWARNING
  56. DECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';
  57. #方法5:使用NOT FOUND
  58. DECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';
  59. #方法6:使用SQLEXCEPTION
  60. DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';
  61. #2.4 案例的处理
  62. DROP PROCEDURE UpdateDataNoCondition;
  63. #重新定义存储过程,体现错误的处理程序
  64. DELIMITER //
  65. CREATE PROCEDURE UpdateDataNoCondition()
  66.         BEGIN
  67.                 #声明处理程序
  68.                 #处理方式1:
  69.                 DECLARE CONTINUE HANDLER FOR 1048 SET @prc_value = -1;
  70.                 #处理方式2:
  71.                 #DECLARE CONTINUE HANDLER FOR sqlstate '23000' SET @prc_value = -1;
  72.                
  73.                 SET @x = 1;
  74.                 UPDATE employees SET email = NULL WHERE last_name = 'Abel';
  75.                 SET @x = 2;
  76.                 UPDATE employees SET email = 'aabbel' WHERE last_name = 'Abel';
  77.                 SET @x = 3;
  78.         END //
  79. DELIMITER ;
  80. #调用存储过程:
  81. CALL UpdateDataNoCondition();
  82. #查看变量:
  83. SELECT @x,@prc_value;
  84. #2.5 再举一个例子:
  85. #创建一个名称为“InsertDataWithCondition”的存储过程
  86. #① 准备工作
  87. CREATE TABLE departments
  88. AS
  89. SELECT * FROM atguigudb.`departments`;
  90. DESC departments;
  91. ALTER TABLE departments
  92. ADD CONSTRAINT uk_dept_name UNIQUE(department_id);
  93. #② 定义存储过程:
  94. DELIMITER //
  95. CREATE PROCEDURE InsertDataWithCondition()
  96.         BEGIN               
  97.                 SET @x = 1;
  98.                 INSERT INTO departments(department_name) VALUES('测试');
  99.                 SET @x = 2;
  100.                 INSERT INTO departments(department_name) VALUES('测试');
  101.                 SET @x = 3;
  102.         END //
  103. DELIMITER ;
  104. #③ 调用
  105. CALL InsertDataWithCondition();
  106. SELECT @x;  #2
  107. #④ 删除此存储过程
  108. DROP PROCEDURE IF EXISTS InsertDataWithCondition;
  109. #⑤ 重新定义存储过程(考虑到错误的处理程序)
  110. DELIMITER //
  111. CREATE PROCEDURE InsertDataWithCondition()
  112.         BEGIN               
  113.                
  114.                 #处理程序
  115.                 #方式1:
  116.                 #declare exit handler for 1062 set @pro_value = -1;
  117.                 #方式2:
  118.                 #declare exit handler for sqlstate '23000' set @pro_value = -1;
  119.                 #方式3:
  120.                 #定义条件
  121.                 DECLARE duplicate_entry CONDITION FOR 1062;
  122.                 DECLARE EXIT HANDLER FOR duplicate_entry SET @pro_value = -1;
  123.                
  124.                 SET @x = 1;
  125.                 INSERT INTO departments(department_name) VALUES('测试');
  126.                 SET @x = 2;
  127.                 INSERT INTO departments(department_name) VALUES('测试');
  128.                 SET @x = 3;
  129.         END //
  130. DELIMITER ;
  131. #调用
  132. CALL InsertDataWithCondition();
  133. SELECT @x,@pro_value;
  134. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
复制代码
3. 流程控制

办理复杂问题不大概通过一个 SQL 语句完成,我们需要实验多个 SQL 操作。流程控制语句的作用就是控制存储过程中 SQL 语句的实验顺序,是我们完成复杂操作必不可少的一部分。只要是实验的程序,流程就分为三大类:
**顺序布局**:
程序从上往下依次实验
**分支布局**:
程序按条件举行选择实验,从两条或多条路径中选择一条实验
**循环布局:**
程序满意一定条件下,重复实验一组语句
针对于MySQL 的流程控制语句主要有 3 类。留意:只能用于存储程序。

3.1 分支布局之 IF

IF 语句的语法布局是:
  1. IF 表达式1 THEN 操作1
  2. [ELSEIF 表达式2 THEN 操作2]……
  3. [ELSE 操作N]
  4. END IF
复制代码
根据表达式的结果为TRUE或FALSE实验相应的语句。这里“[]”中的内容是可选的。
特点:
① 差别的表达式对应差别的操作
② 使用在begin end中
举例1:
  1. IF val IS NULL
  2. THEN SELECT 'val is null';
  3. ELSE SELECT 'val is not null';
  4. END IF;
复制代码
举例2:声明存储过程“update_salary_by_eid1”,定义IN参数emp_id,输入员工编号。判断该员工薪资假如低于8000元并且入职时间高出5年,就涨薪500元;否则就稳固。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_by_eid1(IN emp_id INT)
  3. BEGIN
  4. DECLARE emp_salary DOUBLE;
  5. DECLARE hire_year DOUBLE;
  6. SELECT salary INTO emp_salary FROM employees WHERE employee_id = emp_id;
  7. SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year FROM employees WHERE employee_id = emp_id;
  8. IF emp_salary < 8000 AND hire_year > 5
  9. THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
  10. END IF;
  11. END //
  12. DELIMITER ;
复制代码
举例3:声明存储过程“update_salary_by_eid2”,定义IN参数emp_id,输入员工编号。判断该员工薪资假如低于9000元并且入职时间高出5年,就涨薪500元;否则就涨薪100元。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_by_eid2(IN emp_id INT)
  3. BEGIN
  4. DECLARE emp_salary DOUBLE;
  5. DECLARE hire_year DOUBLE;
  6. SELECT salary INTO emp_salary FROM employees WHERE employee_id = emp_id;
  7. SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year FROM employees WHERE employee_id = emp_id;
  8. IF emp_salary < 8000 AND hire_year > 5
  9. THEN UPDATE employees SET salary = salary + 500 WHERE employee_id =emp_id;
  10. ELSE
  11. UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
  12. END IF;
  13. END //
  14. DELIMITER ;
复制代码
举例4:声明存储过程“update_salary_by_eid3”,定义IN参数emp_id,输入员工编号。判断该员工薪资假如低于9000元,就更新薪资为9000元;薪资假如大于等于9000元且低于10000的,但是奖金比例为NULL的,就更新奖金比例为0.01;其他的涨薪100元。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_by_eid3(IN emp_id INT)
  3. BEGIN
  4. DECLARE emp_salary DOUBLE;
  5. DECLARE bonus DECIMAL(3,2);
  6. SELECT salary INTO emp_salary FROM employees WHERE employee_id = emp_id; SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id;
  7. IF emp_salary < 9000
  8. THEN UPDATE employees SET salary = 9000 WHERE employee_id = emp_id;
  9. ELSEIF emp_salary < 10000 AND bonus IS NULL
  10. THEN UPDATE employees SET commission_pct = 0.01 WHERE employee_id =emp_id;
  11. ELSE
  12. UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
  13. END IF;
  14. END //
  15. DELIMITER ;
  16. 1234567891011121314151617181920
复制代码
3.2 分支布局之 CASE

CASE 语句的语法布局1:
  1. #情况一:类似于switch
  2. CASE 表达式
  3. WHEN 值1 THEN 结果1或语句1(如果是语句,需要加分号)
  4. WHEN 值2 THEN 结果2或语句2(如果是语句,需要加分号)
  5. ...
  6. ELSE 结果n或语句n(如果是语句,需要加分号)
  7. END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
  8. CASE 语句的语法结构2:
  9. #情况二:类似于多重if
  10. CASE
  11. WHEN 条件1 THEN 结果1或语句1(如果是语句,需要加分号)
  12. WHEN 条件2 THEN 结果2或语句2(如果是语句,需要加分号)
  13. ...
  14. ELSE 结果n或语句n(如果是语句,需要加分号)
  15. END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
  16. 1234567891011121314151617
复制代码
举例1:
使用CASE流程控制语句的第1种格式,判断val值等于1、等于2,或者两者都不等。
  1. CASE val
  2. WHEN 1 THEN SELECT 'val is 1';
  3. WHEN 2 THEN SELECT 'val is 2';
  4. ELSE SELECT 'val is not 1 or 2';
  5. END CASE;
  6. 12345
复制代码
举例2:
使用CASE流程控制语句的第2种格式,判断val是否为空、小于0、大于0或者等于0。
  1. CASE
  2. WHEN val IS NULL THEN SELECT 'val is null';
  3. WHEN val < 0 THEN SELECT 'val is less than 0';
  4. WHEN val > 0 THEN SELECT 'val is greater than 0';
  5. ELSE SELECT 'val is 0';
  6. END CASE;
  7. 123456
复制代码
举例3:
声明存储过程“update_salary_by_eid4”,定义IN参数emp_id,输入员工编号。判断该员工薪资假如低于9000元,就更新薪资为9000元;薪资大于等于9000元且低于10000的,但是奖金比例为NULL的,就更新奖金比例为0.01;其他的涨薪100元。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_by_eid4(IN emp_id INT)
  3. BEGIN
  4. DECLARE emp_sal DOUBLE;
  5. DECLARE bonus DECIMAL(3,2);
  6. SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  7. SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id;
  8. CASE
  9. WHEN emp_sal<9000
  10. THEN UPDATE employees SET salary=9000 WHERE employee_id = emp_id;
  11. WHEN emp_sal<10000 AND bonus IS NULL
  12. THEN UPDATE employees SET commission_pct=0.01 WHERE employee_id = emp_id;
  13. ELSE
  14. UPDATE employees SET salary=salary+100 WHERE employee_id = emp_id;
  15. END CASE;
  16. END //
  17. DELIMITER ;
  18. 123456789101112131415161718192021
复制代码
举例4:声明存储过程update_salary_by_eid5,定义IN参数emp_id,输入员工编号。判断该员工的入职年限,假如是0年,薪资涨50;假如是1年,薪资涨100;假如是2年,薪资涨200;假如是3年,薪资涨300;假如是4年,薪资涨400;其他的涨薪500。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_by_eid5(IN emp_id INT)
  3. BEGIN
  4. DECLARE emp_sal DOUBLE;
  5. DECLARE hire_year DOUBLE;
  6. SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  7. SELECT ROUND(DATEDIFF(CURDATE(),hire_date)/365) INTO hire_year FROM employees WHERE employee_id = emp_id;
  8. CASE hire_year
  9. WHEN 0 THEN UPDATE employees SET salary=salary+50 WHERE employee_id = emp_id;
  10. WHEN 1 THEN UPDATE employees SET salary=salary+100 WHERE employee_id = emp_id;
  11. WHEN 2 THEN UPDATE employees SET salary=salary+200 WHERE employee_id = emp_id;
  12. WHEN 3 THEN UPDATE employees SET salary=salary+300 WHERE employee_id = emp_id;
  13. WHEN 4 THEN UPDATE employees SET salary=salary+400 WHERE employee_id = emp_id;
  14. ELSE UPDATE employees SET salary=salary+500 WHERE employee_id = emp_id;
  15. END CASE;
  16. END //
  17. DELIMITER ;
  18. 1234567891011121314151617181920212223242526
复制代码
3.3 循环布局之LOOP

LOOP循环语句用来重复实验某些语句。LOOP内的语句不停重复实验直到循环被退出(使用LEAVE子句),跳出循环过程。
LOOP语句的基本格式如下:
  1. [loop_label:] LOOP
  2. 循环执行的语句
  3. END LOOP [loop_label]
  4. 123
复制代码
此中,loop_label表示LOOP语句的标注名称,该参数可以省略。
举例1:
使用LOOP语句举行循环操作,id值小于10时将重复实验循环过程。
  1. DECLARE id INT DEFAULT 0;
  2. add_loop:LOOP
  3. SET id = id +1;
  4. IF id >= 10 THEN LEAVE add_loop;
  5. END IF;
  6. END LOOP add_loop;
  7. 12345678910
复制代码
举例2:
当市场情况变好时,公司为了嘉奖各人,决定给各人涨工资。声明存储过程
“update_salary_loop()”,声明OUT参数num,输出循环次数。存储过程中实现循环给各人涨薪,薪资涨为原来的1.1倍。直到全公司的平均薪资达到12000结束。并统计循环次数。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_loop(OUT num INT)
  3. BEGIN
  4. DECLARE avg_salary DOUBLE;
  5. DECLARE loop_count INT DEFAULT 0;
  6. SELECT AVG(salary) INTO avg_salary FROM employees;
  7. label_loop:LOOP
  8. IF avg_salary >= 12000 THEN LEAVE label_loop;
  9. END IF;
  10. UPDATE employees SET salary = salary * 1.1;
  11. SET loop_count = loop_count + 1;
  12. SELECT AVG(salary) INTO avg_salary FROM employees;
  13. END LOOP label_loop;
  14. SET num = loop_count;
  15. END //
  16. DELIMITER ;
  17. 12345678910111213141516171819
复制代码
3.4 循环布局之WHILE

WHILE语句创建一个带条件判断的循环过程。WHILE在实验语句实验时,先对指定的表达式举行判断,假如为真,就实验循环内的语句,否则退出循环。WHILE语句的基本格式如下:
  1. [while_label:] WHILE 循环条件        DO
  2. 循环体
  3. END WHILE [while_label];
  4. 12345
复制代码
while_label为WHILE语句的标注名称;假如循环条件结果为真,WHILE语句内的语句或语句群被实验,直至循环条件为假,退出循环。
举例1:
WHILE语句示例,i值小于10时,将重复实验循环过程,代码如下:
  1. DELIMITER //
  2. CREATE PROCEDURE test_while()
  3. BEGIN
  4. DECLARE i INT DEFAULT 0;
  5. WHILE i < 10 DO
  6. SET i = i + 1;
  7. END WHILE;
  8. SELECT i;
  9. END //
  10. DELIMITER ;
  11. 12345678910
  12. #调用
  13. CALL test_while();
  14. 12
复制代码
举例2:
市场情况不好时,公司为了渡过难关,决定临时低落各人的薪资。声明存储过程
“update_salary_while()”,声明OUT参数num,输出循环次数。存储过程中实现循环给各人降薪,薪资降为原来的90%。直到全公司的平均薪资达到5000结束。并统计循环次数。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_while(OUT num INT)
  3. BEGIN
  4. DECLARE avg_sal DOUBLE ;
  5. DECLARE while_count INT DEFAULT 0;
  6. SELECT AVG(salary) INTO avg_sal FROM employees;
  7. WHILE avg_sal > 5000 DO
  8. UPDATE employees SET salary = salary * 0.9;
  9. SET while_count = while_count + 1;
  10. SELECT AVG(salary) INTO avg_sal FROM employees; END WHILE;
  11. SET num = while_count;
  12. END //
  13. DELIMITER ;
  14. 123456789101112131415161718
复制代码
3.5 循环布局之REPEAT

REPEAT语句创建一个带条件判断的循环过程。与WHILE循环差别的是,REPEAT 循环首先会实验一次循环,然后在 UNTIL 中举行表达式的判断,假如满意条件就退出,即 END REPEAT;假如条件不满意,则会就继承实验循环,直到满意退出条件为止。
REPEAT语句的基本格式如下:
  1. [repeat_label:] REPEAT
  2. 循环体的语句
  3. UNTIL 结束循环的条件表达式
  4. END REPEAT [repeat_label]
  5. 12345
复制代码
repeat_label为REPEAT语句的标注名称,该参数可以省略;REPEAT语句内的语句或语句群被重复,直至expr_condition为真。
举例1:
  1. DELIMITER //
  2. CREATE PROCEDURE test_repeat()
  3. BEGIN
  4. DECLARE i INT DEFAULT 0;
  5. REPEAT
  6. SET i = i + 1;
  7. UNTIL i >= 10
  8. END REPEAT;
  9. SELECT i;
  10. END //
  11. DELIMITER ;
  12. 12345678910111213
复制代码
举例2:
当市场情况变好时,公司为了嘉奖各人,决定给各人涨工资。声明存储过程
“update_salary_repeat()”,声明OUT参数num,输出循环次数。存储过程中实现循环给各人涨薪,薪资涨为原来的1.15倍。直到全公司的平均薪资达到13000结束。并统计循环次数。
  1. DELIMITER //
  2. CREATE PROCEDURE update_salary_repeat(OUT num INT)
  3. BEGIN
  4. DECLARE avg_sal DOUBLE ;
  5. DECLARE repeat_count INT DEFAULT 0;
  6. SELECT AVG(salary) INTO avg_sal FROM employees;
  7. REPEAT
  8. UPDATE employees SET salary = salary * 1.15;
  9. SET repeat_count = repeat_count + 1;
  10. SELECT AVG(salary) INTO avg_sal FROM employees;
  11. UNTIL avg_sal >= 13000
  12. END REPEAT;
  13. SET num = repeat_count;
  14. END //
  15. DELIMITER ;
  16. 123456789101112131415161718192021
复制代码
对比三种循环布局:

1、这三种循环都可以省略名称,但假如循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名称。
2、 LOOP:一般用于实现简朴的"死"循环 WHILE:先判断后实验 REPEAT:先实验后判断,无条件至少实验一次
3.6 跳转语句之LEAVE语句

LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出程序体的操作。假如你有面向过程的编程语言的使用经验,你可以把 LEAVE 明白为 break。
基本格式如下:
  1. LEAVE 标记名
  2. 1
复制代码
此中,label参数表示循环的标记。LEAVE和BEGIN … END或循环一起被使用。
举例1:
创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN…END加标记名,并在BEGIN…END中使用IF语句判断num参数的值。
假如num<=0,则使用LEAVE语句退出BEGIN…END;
假如num=1,则查询“employees”表的平均薪资;
假如num=2,则查询“employees”表的最低薪资;
假如num>2,则查询“employees”表的最高薪资。
IF语句结束后查询“employees”表的总人数。
  1. DELIMITER //
  2. CREATE PROCEDURE leave_begin(IN num INT)
  3. begin_label: BEGIN
  4. IF num<=0
  5. THEN LEAVE begin_label;
  6. ELSEIF num=1
  7. THEN SELECT AVG(salary) FROM employees;
  8. ELSEIF num=2
  9. THEN SELECT MIN(salary) FROM employees;
  10. ELSE
  11. SELECT MAX(salary) FROM employees;
  12. END IF;
  13. SELECT COUNT(*) FROM employees;
  14. END //
  15. DELIMITER ;
  16. 12345678910111213141516
复制代码
举例2:
当市场情况不好时,公司为了渡过难关,决定临时低落各人的薪资。声明存储过程“leave_while()”,声明OUT参数num,输出循环次数,存储过程中使用WHILE循环给各人低落薪资为原来薪资的90%,直到全公司的平均薪资小于等于10000,并统计循环次数。
  1. DELIMITER //
  2. CREATE PROCEDURE leave_while(OUT num INT)
  3. BEGIN
  4. #
  5. DECLARE avg_sal DOUBLE;#记录平均工资
  6. DECLARE while_count INT DEFAULT 0; #记录循环次数
  7. SELECT AVG(salary) INTO avg_sal FROM employees; #① 初始化条件
  8. while_label:WHILE TRUE DO        #② 循环条件
  9. #③ 循环体
  10. IF avg_sal <= 10000 THEN
  11. LEAVE while_label;
  12. END IF;
  13. UPDATE employees SET salary        = salary * 0.9;
  14. SET while_count = while_count + 1;
  15. #④ 迭代条件
  16. SELECT AVG(salary) INTO avg_sal FROM employees;
  17. END WHILE;
  18. #赋值
  19. SET num = while_count;
  20. END //
  21. DELIMITER ;
  22. 123456789101112131415161718192021222324252627282930
复制代码
3.7 跳转语句之ITERATE语句

ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将实验顺序转到语句段开头处。假如你有面向过程的编程语言的使用经验,你可以把 ITERATE 明白为 continue,意思为“再次循环”。
语句基本格式如下:
  1. ITERATE label
  2. 1
复制代码
label参数表示循环的标记。ITERATE语句必须跟在循环标记前面。
举例:
定义局部变量num,初始值为0。循环布局中实验num + 1操作。
假如num < 10,则继承实验循环;
假如num > 15,则退出循环布局;
  1. DELIMITER //
  2. CREATE PROCEDURE test_iterate()
  3. BEGIN
  4. DECLARE num INT DEFAULT 0;
  5. my_loop:LOOP
  6. SET num = num + 1;
  7. IF num < 10
  8. THEN ITERATE my_loop;
  9. ELSEIF num > 15
  10. THEN LEAVE my_loop;
  11. END IF;
  12. SELECT '尚硅谷:让天下没有难学的技术';
  13. END LOOP my_loop;
  14. END //
  15. DELIMITER ;
  16. 12345678910111213141516
复制代码
演示代码

  1. #3. 流程控制
  2. #3.1 分支结构之 IF
  3. #举例1
  4. DELIMITER //
  5. CREATE PROCEDURE test_if()
  6. BEGIN       
  7.         #情况1:
  8.         #声明局部变量
  9.         #declare stu_name varchar(15);
  10.        
  11.         #if stu_name is null
  12.         #        then select 'stu_name is null';
  13.         #end if;
  14.        
  15.         #情况2:二选一
  16.         #declare email varchar(25) default 'aaa';
  17.        
  18.         #if email is null
  19.         #        then select 'email is null';
  20.         #else
  21.         #        select 'email is not null';
  22.         #end if;
  23.        
  24.         #情况3:多选一
  25.         DECLARE age INT DEFAULT 20;
  26.        
  27.         IF age > 40
  28.                 THEN SELECT '中老年';
  29.         ELSEIF age > 18
  30.                 THEN SELECT '青壮年';
  31.         ELSEIF age > 8
  32.                 THEN SELECT '青少年';
  33.         ELSE
  34.                 SELECT '婴幼儿';
  35.         END IF;
  36.        
  37. END //
  38. DELIMITER ;
  39. #调用
  40. CALL test_if();
  41. DROP PROCEDURE test_if;
  42. #举例2:声明存储过程“update_salary_by_eid1”,定义IN参数emp_id,输入员工编号。
  43. #判断该员工薪资如果低于8000元并且入职时间超过5年,就涨薪500元;否则就不变。
  44. DELIMITER //
  45. CREATE PROCEDURE update_salary_by_eid1(IN emp_id INT)
  46. BEGIN
  47.         #声明局部变量
  48.         DECLARE emp_sal DOUBLE; #记录员工的工资
  49.         DECLARE hire_year DOUBLE; #记录员工入职公司的年头
  50.        
  51.        
  52.         #赋值
  53.         SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  54.        
  55.         SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year FROM employees WHERE employee_id = emp_id;
  56.        
  57.         #判断
  58.         IF emp_sal < 8000 AND hire_year >= 5
  59.                 THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
  60.         END IF;
  61. END //
  62. DELIMITER ;
  63. #调用存储过程
  64. CALL update_salary_by_eid1(104);
  65. SELECT DATEDIFF(CURDATE(),hire_date)/365, employee_id,salary
  66. FROM employees
  67. WHERE salary < 8000 AND DATEDIFF(CURDATE(),hire_date)/365 >= 5;
  68. DROP PROCEDURE update_salary_by_eid1;
  69. #举例3:声明存储过程“update_salary_by_eid2”,定义IN参数emp_id,输入员工编号。
  70. #判断该员工薪资如果低于9000元并且入职时间超过5年,就涨薪500元;否则就涨薪100元。
  71. DELIMITER //
  72. CREATE PROCEDURE update_salary_by_eid2(IN emp_id INT)
  73. BEGIN
  74.         #声明局部变量
  75.         DECLARE emp_sal DOUBLE; #记录员工的工资
  76.         DECLARE hire_year DOUBLE; #记录员工入职公司的年头
  77.        
  78.        
  79.         #赋值
  80.         SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  81.        
  82.         SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year FROM employees WHERE employee_id = emp_id;
  83.        
  84.         #判断
  85.         IF emp_sal < 9000 AND hire_year >= 5
  86.                 THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
  87.         ELSE
  88.                 UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
  89.         END IF;
  90. END //
  91. DELIMITER ;
  92. #调用
  93. CALL update_salary_by_eid2(103);
  94. CALL update_salary_by_eid2(104);
  95. SELECT * FROM employees
  96. WHERE employee_id IN (103,104);
  97. #举例4:声明存储过程“update_salary_by_eid3”,定义IN参数emp_id,输入员工编号。
  98. #判断该员工薪资如果低于9000元,就更新薪资为9000元;薪资如果大于等于9000元且
  99. #低于10000的,但是奖金比例为NULL的,就更新奖金比例为0.01;其他的涨薪100元。
  100. DELIMITER //
  101. CREATE PROCEDURE update_salary_by_eid3(IN emp_id INT)
  102. BEGIN
  103.         #声明变量
  104.         DECLARE emp_sal DOUBLE; #记录员工工资
  105.         DECLARE bonus DOUBLE; #记录员工的奖金率
  106.        
  107.         #赋值
  108.         SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  109.         SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id;
  110.        
  111.        
  112.         #判断
  113.         IF emp_sal < 9000
  114.                 THEN UPDATE employees SET salary = 9000 WHERE employee_id = emp_id;
  115.         ELSEIF emp_sal < 10000 AND bonus IS NULL
  116.                 THEN UPDATE employees SET commission_pct = 0.01 WHERE employee_id = emp_id;
  117.         ELSE
  118.                 UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
  119.         END IF;
  120. END //
  121. DELIMITER ;
  122. #调用
  123. CALL update_salary_by_eid3(102);
  124. CALL update_salary_by_eid3(103);
  125. CALL update_salary_by_eid3(104);
  126. SELECT *
  127. FROM employees
  128. WHERE employee_id IN (102,103,104);
  129. ##3.2 分支结构之case
  130. #举例1:基本使用
  131. DELIMITER //
  132. CREATE PROCEDURE test_case()
  133. BEGIN
  134.         #演示1:case ... when ...then ...
  135.         /*
  136.         declare var int default 2;
  137.        
  138.         case var
  139.                 when 1 then select 'var = 1';
  140.                 when 2 then select 'var = 2';
  141.                 when 3 then select 'var = 3';
  142.                 else select 'other value';
  143.         end case;
  144.         */
  145.         #演示2:case when ... then ....
  146.         DECLARE var1 INT DEFAULT 10;
  147.         CASE
  148.         WHEN var1 >= 100 THEN SELECT '三位数';
  149.         WHEN var1 >= 10 THEN SELECT '两位数';
  150.         ELSE SELECT '个数位';
  151.         END CASE;
  152. END //
  153. DELIMITER ;
  154. #调用
  155. CALL test_case();
  156. DROP PROCEDURE test_case;
  157. #举例2:声明存储过程“update_salary_by_eid4”,定义IN参数emp_id,输入员工编号。
  158. #判断该员工薪资如果低于9000元,就更新薪资为9000元;薪资大于等于9000元且低于10000的,
  159. #但是奖金比例为NULL的,就更新奖金比例为0.01;其他的涨薪100元。
  160. DELIMITER //
  161. CREATE PROCEDURE update_salary_by_eid4(IN emp_id INT)
  162. BEGIN
  163.         #局部变量的声明
  164.         DECLARE emp_sal DOUBLE; #记录员工的工资
  165.         DECLARE bonus DOUBLE; #记录员工的奖金率
  166.        
  167.         #局部变量的赋值
  168.         SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
  169.         SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id;
  170.        
  171.         CASE
  172.         WHEN emp_sal < 9000 THEN UPDATE employees SET salary = 9000 WHERE employee_id = emp_id;
  173.         WHEN emp_sal < 10000 AND bonus IS NULL THEN UPDATE employees SET commission_pct = 0.01
  174.                                                     WHERE employee_id = emp_id;
  175.         ELSE UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
  176.         END CASE;
  177.        
  178. END //
  179. DELIMITER ;
  180. #调用
  181. CALL update_salary_by_eid4(103);
  182. CALL update_salary_by_eid4(104);
  183. CALL update_salary_by_eid4(105);
  184. SELECT *
  185. FROM employees
  186. WHERE employee_id IN (103,104,105);
  187. #举例3:声明存储过程update_salary_by_eid5,定义IN参数emp_id,输入员工编号。
  188. #判断该员工的入职年限,如果是0年,薪资涨50;如果是1年,薪资涨100;
  189. #如果是2年,薪资涨200;如果是3年,薪资涨300;如果是4年,薪资涨400;其他的涨薪500。
  190. DELIMITER //
  191. CREATE PROCEDURE update_salary_by_eid5(IN emp_id INT)
  192. BEGIN
  193.         #声明局部变量
  194.         DECLARE hire_year INT; #记录员工入职公司的总时间(单位:年)
  195.        
  196.         #赋值
  197.         SELECT ROUND(DATEDIFF(CURDATE(),hire_date) / 365) INTO hire_year
  198.         FROM employees WHERE employee_id = emp_id;
  199.        
  200.         #判断
  201.         CASE hire_year
  202.                 WHEN 0 THEN UPDATE employees SET salary = salary + 50 WHERE employee_id = emp_id;
  203.                 WHEN 1 THEN UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
  204.                 WHEN 2 THEN UPDATE employees SET salary = salary + 200 WHERE employee_id = emp_id;
  205.                 WHEN 3 THEN UPDATE employees SET salary = salary + 300 WHERE employee_id = emp_id;
  206.                 WHEN 4 THEN UPDATE employees SET salary = salary + 400 WHERE employee_id = emp_id;
  207.                 ELSE UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
  208.         END CASE;
  209. END //
  210. DELIMITER ;
  211. #调用
  212. CALL update_salary_by_eid5(101);
  213. SELECT *
  214. FROM employees
  215. DROP PROCEDURE update_salary_by_eid5;
  216. #4.1 循环结构之LOOP
  217. /*
  218. [loop_label:] LOOP
  219.         循环执行的语句
  220. END LOOP [loop_label]
  221. */
  222. #举例1:
  223. DELIMITER //
  224. CREATE PROCEDURE test_loop()
  225. BEGIN
  226.         #声明局部变量
  227.         DECLARE num INT DEFAULT 1;
  228.        
  229.         loop_label:LOOP
  230.                 #重新赋值
  231.                 SET num = num + 1;
  232.                
  233.                 #可以考虑某个代码程序反复执行。(略)
  234.                
  235.                 IF num >= 10 THEN LEAVE loop_label;
  236.                 END IF;
  237.         END LOOP loop_label;
  238.        
  239.         #查看num
  240.         SELECT num;
  241. END //
  242. DELIMITER ;
  243. #调用
  244. CALL test_loop();
  245. /*
  246. +------+
  247. | num  |
  248. +------+
  249. |   10 |
  250. +------+
  251. */
  252. #举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。
  253. #声明存储过程“update_salary_loop()”,声明OUT参数num,输出循环次数。
  254. #存储过程中实现循环给大家涨薪,薪资涨为原来的1.1倍。直到全公司的平
  255. #均薪资达到12000结束。并统计循环次数。
  256. DELIMITER //
  257. CREATE PROCEDURE update_salary_loop(OUT num INT)
  258. BEGIN
  259.         #声明变量
  260.         DECLARE avg_sal DOUBLE ; #记录员工的平均工资
  261.        
  262.         DECLARE loop_count INT DEFAULT 0;#记录循环的次数
  263.        
  264.         #① 初始化条件
  265.         #获取员工的平均工资
  266.         SELECT AVG(salary) INTO avg_sal FROM employees;
  267.        
  268.         loop_lab:LOOP
  269.                 #② 循环条件
  270.                 #结束循环的条件
  271.                 IF avg_sal >= 12000
  272.                         THEN LEAVE loop_lab;
  273.                 END IF;
  274.                
  275.                 #③ 循环体
  276.                 #如果低于12000,更新员工的工资
  277.                 UPDATE employees SET salary = salary * 1.1;
  278.                
  279.                 #④ 迭代条件
  280.                 #更新avg_sal变量的值
  281.                 SELECT AVG(salary) INTO avg_sal FROM employees;
  282.                
  283.                 #记录循环次数
  284.                 SET loop_count = loop_count + 1;
  285.                
  286.         END LOOP loop_lab;
  287.                        
  288.         #给num赋值
  289.         SET num = loop_count;       
  290. END //
  291. DELIMITER ;
  292. SELECT AVG(salary) FROM employees;
  293. CALL update_salary_loop(@num);
  294. SELECT @num;
  295. #4.2 循环结构之WHILE
  296. /*
  297. [while_label:] WHILE 循环条件  DO
  298.         循环体
  299. END WHILE [while_label];
  300. */
  301. #举例1:
  302. DELIMITER //
  303. CREATE PROCEDURE test_while()
  304. BEGIN       
  305.         #初始化条件
  306.         DECLARE num INT DEFAULT 1;
  307.         #循环条件
  308.         WHILE num <= 10 DO
  309.                 #循环体(略)
  310.                
  311.                 #迭代条件
  312.                 SET num = num + 1;
  313.         END WHILE;
  314.         #查询
  315.         SELECT num;
  316. END //
  317. DELIMITER ;
  318. #调用
  319. CALL test_while();
  320. /*输出
  321. +------+
  322. | num  |
  323. +------+
  324. |   11 |
  325. +------+
  326. */
  327. #举例2:市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。
  328. #声明存储过程“update_salary_while()”,声明OUT参数num,输出循环次数。
  329. #存储过程中实现循环给大家降薪,薪资降为原来的90%。直到全公司的平均薪资
  330. #达到5000结束。并统计循环次数。
  331. DELIMITER //
  332. CREATE PROCEDURE update_salary_while(OUT num INT)
  333. BEGIN
  334.         #声明变量
  335.         DECLARE avg_sal DOUBLE ; #记录平均工资
  336.         DECLARE while_count INT DEFAULT 0; #记录循环次数
  337.         #赋值
  338.         SELECT AVG(salary) INTO avg_sal FROM employees;
  339.        
  340.         WHILE avg_sal > 5000 DO
  341.                 UPDATE employees SET salary = salary * 0.9 ;
  342.                 SET while_count = while_count + 1;
  343.                
  344.                 SELECT AVG(salary) INTO avg_sal FROM employees;
  345.                
  346.         END WHILE;
  347.        
  348.         #给num赋值
  349.         SET num = while_count;               
  350. END //
  351. DELIMITER ;
  352. #调用
  353. CALL update_salary_while(@num);
  354. SELECT @num;
  355. SELECT AVG(salary) FROM employees;
  356. #4.3 循环结构之REPEAT
  357. /*
  358. [repeat_label:] REPEAT
  359.     循环体的语句
  360. UNTIL 结束循环的条件表达式
  361. END REPEAT [repeat_label]
  362. */
  363. #举例1:
  364. DELIMITER //
  365. CREATE PROCEDURE test_repeat()
  366. BEGIN
  367.         #声明变量
  368.         DECLARE num INT DEFAULT 1;
  369.        
  370.         REPEAT
  371.                 SET num = num + 1;
  372.                 UNTIL num >= 10
  373.         END REPEAT;
  374.        
  375.         #查看
  376.         SELECT num;
  377. END //
  378. DELIMITER ;
  379. #调用
  380. CALL test_repeat();
  381. #举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。
  382. #声明存储过程“update_salary_repeat()”,声明OUT参数num,输出循环次数。
  383. #存储过程中实现循环给大家涨薪,薪资涨为原来的1.15倍。直到全公司的平均
  384. #薪资达到13000结束。并统计循环次数。
  385. DELIMITER //
  386. CREATE PROCEDURE update_salary_repeat(OUT num INT)
  387. BEGIN
  388.         #声明变量
  389.         DECLARE avg_sal DOUBLE ; #记录平均工资
  390.         DECLARE repeat_count INT DEFAULT 0; #记录循环次数
  391.        
  392.         #赋值
  393.         SELECT AVG(salary) INTO avg_sal FROM employees;
  394.        
  395.         REPEAT
  396.                 UPDATE employees SET salary = salary * 1.15;
  397.                 SET repeat_count = repeat_count + 1;
  398.                
  399.                 SELECT AVG(salary) INTO avg_sal FROM employees;
  400.                
  401.                 UNTIL avg_sal >= 13000
  402.        
  403.         END REPEAT;
  404.        
  405.         #给num赋值
  406.         SET num = repeat_count;               
  407.                
  408. END //
  409. DELIMITER ;
  410. #调用
  411. CALL update_salary_repeat(@num);
  412. SELECT @num;
  413. SELECT AVG(salary) FROM employees;
  414. /*
  415. 凡是循环结构,一定具备4个要素:
  416. 1. 初始化条件
  417. 2. 循环条件
  418. 3. 循环体
  419. 4. 迭代条件
  420. */
  421. #5.1 LEAVE的使用
  422. /*
  423. **举例1:**创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN...END加标记名,
  424. 并在BEGIN...END中使用IF语句判断num参数的值。
  425. - 如果num<=0,则使用LEAVE语句退出BEGIN...END;
  426. - 如果num=1,则查询“employees”表的平均薪资;
  427. - 如果num=2,则查询“employees”表的最低薪资;
  428. - 如果num>2,则查询“employees”表的最高薪资。
  429. IF语句结束后查询“employees”表的总人数。
  430. */
  431. DELIMITER //
  432. CREATE PROCEDURE leave_begin(IN num INT)
  433. begin_label:BEGIN
  434.         IF num <= 0
  435.                 THEN LEAVE begin_label;
  436.         ELSEIF num = 1
  437.                 THEN SELECT AVG(salary) FROM employees;
  438.         ELSEIF num = 2
  439.                 THEN SELECT MIN(salary) FROM employees;
  440.         ELSE
  441.                 SELECT MAX(salary) FROM employees;
  442.         END IF;
  443.        
  444.         #查询总人数
  445.         SELECT COUNT(*) FROM employees;
  446. END //
  447. DELIMITER ;
  448. #调用
  449. CALL leave_begin(1);
  450. #举例2:当市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。
  451. #声明存储过程“leave_while()”,声明OUT参数num,输出循环次数,存储过程中使用WHILE
  452. #循环给大家降低薪资为原来薪资的90%,直到全公司的平均薪资小于等于10000,并统计循环次数。
  453. DELIMITER //
  454. CREATE PROCEDURE leave_while(OUT num INT)
  455. BEGIN
  456.         #
  457.         DECLARE avg_sal DOUBLE;#记录平均工资
  458.         DECLARE while_count INT DEFAULT 0; #记录循环次数
  459.        
  460.         SELECT AVG(salary) INTO avg_sal FROM employees; #① 初始化条件
  461.        
  462.         while_label:WHILE TRUE DO  #② 循环条件
  463.                
  464.                 #③ 循环体
  465.                 IF avg_sal <= 10000 THEN
  466.                         LEAVE while_label;
  467.                 END IF;
  468.                
  469.                 UPDATE employees SET salary  = salary * 0.9;
  470.                 SET while_count = while_count + 1;
  471.                
  472.                 #④ 迭代条件
  473.                 SELECT AVG(salary) INTO avg_sal FROM employees;
  474.        
  475.         END WHILE;
  476.        
  477.         #赋值
  478.         SET num = while_count;
  479. END //
  480. DELIMITER ;
  481. #调用
  482. CALL leave_while(@num);
  483. SELECT @num;
  484. SELECT AVG(salary) FROM employees;
  485. #5.2 ITERATE的使用
  486. /*
  487. 举例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作。
  488. - 如果num < 10,则继续执行循环;
  489. - 如果num > 15,则退出循环结构;
  490. */
  491. DELIMITER //
  492. CREATE PROCEDURE test_iterate()
  493. BEGIN
  494.         DECLARE num INT DEFAULT 0;
  495.        
  496.         loop_label:LOOP
  497.                 #赋值
  498.                 SET num = num + 1;
  499.                
  500.                 IF num  < 10
  501.                         THEN ITERATE loop_label;
  502.                 ELSEIF num > 15
  503.                         THEN LEAVE loop_label;
  504.                 END IF;
  505.                
  506.                 SELECT '让天下没有难学的技术';
  507.        
  508.         END LOOP;
  509. END //
  510. DELIMITER ;
  511. CALL test_iterate();
  512. /*
  513. +------------------------+
  514. | :让天下没有难学的技术 |
  515. +------------------------+
  516. |   让天下没有难学的技术 |
  517. +------------------------+
  518. 1 row in set (0.00 sec)
  519. +------------------------+
  520. | :让天下没有难学的技术 |
  521. +------------------------+
  522. |   让天下没有难学的技术 |
  523. +------------------------+
  524. 1 row in set (0.00 sec)
  525. +------------------------+
  526. | :让天下没有难学的技术 |
  527. +------------------------+
  528. |   让天下没有难学的技术 |
  529. +------------------------+
  530. 1 row in set (0.01 sec)
  531. +------------------------+
  532. | :让天下没有难学的技术 |
  533. +------------------------+
  534. |   让天下没有难学的技术 |
  535. +------------------------+
  536. 1 row in set (0.01 sec)
  537. +------------------------+
  538. | :让天下没有难学的技术 |
  539. +------------------------+
  540. |   让天下没有难学的技术 |
  541. +------------------------+
  542. 1 row in set (0.02 sec)
  543. +------------------------+
  544. | :让天下没有难学的技术 |
  545. +------------------------+
  546. |   让天下没有难学的技术 |
  547. +------------------------+
  548. */
  549. SELECT * FROM employees;
  550. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
复制代码
4. 游标

4.1 什么是游标(或光标)

固然可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记载的关键字 LIMIT 返回一条记载,但是,却无法在结果会合像指针一样,向前定位一条记载、向后定位一条记载,或者是随意定位到某一条记载,并对记载的数据举行处理。
这个时候,就可以用到游标。游标,提供了一种机动的操作方式,让我们可以或许对结果会合的每一条记载举行定位,并对指向的记载中的数据举行操作的数据布局。游标让 SQL 这种面向集合的语言有了面向过程开发的能力。
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标 充当了指针的作用,我们可以通过操作游标来对数据行举行操作。
MySQL中游标可以在存储过程和函数中使用。
好比查询了 employees 数据表中工资高于15000的员工都有哪些:
  1. SELECT employee_id,last_name,salary FROM employees WHERE salary > 15000;
复制代码

这里可以通过游标来操作数据行,如图所示此时游标所在的行是“108”的记载,也可以在结果集上滚动游标,指向结果会合的恣意一行。
4.2 使用游标步骤

游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前被声明。
假如我们想要使用游标,一般需要经历四个步骤。差别的 DBMS 中,使用游标的语法大概略有差别。
第一步,声明游标
在MySQL中,使用DECLARE关键字来声明游标,其语法的基本情势如下:
  1. DECLARE cursor_name CURSOR FOR select_statement;
复制代码
这个语法实用于 MySQL,SQL Server,DB2 和 MariaDB。假如是用 Oracle 或者 PostgreSQL,需要写成:
  1. DECLARE cursor_name CURSOR IS select_statement;
复制代码
要使用 SELECT 语句来获取数据结果集,而此时还没有开始遍历数据,这里 select_statement 代表的是 SELECT 语句,返回一个用于创建游标的结果集。
好比:
  1. DECLARE cur_emp CURSOR FOR
  2. SELECT employee_id,salary FROM employees;
  3. DECLARE cursor_fruit CURSOR FOR
  4. SELECT f_name, f_price FROM fruits ;
复制代码
第二步,打开游标
打开游标的语法如下:
  1. OPEN cursor_name
复制代码
当定义好游标之后,假如想要使用游标,必须先打开游标。打开游标的时候 SELECT 语句的查询结果集就会送到游标工作区,为背面游标的逐条读取 结果会合的记载做预备。
  1. OPEN        cur_emp ;
复制代码
第三步,使用游标(从游标中取得数据)
语法如下:
  1. FETCH cursor_name INTO var_name [, var_name] ...
复制代码
这句的作用是使用 cursor_name 这个游标来读取当前行,并且将数据保存到 var_name 这个变量中,游标指针指到下一行。假如游标读取的数据行有多个列名,则在 INTO 关键字背面赋值给多个变量名即可。
留意:var_name必须在声明游标之前就定义好。
  1. FETCH        cur_emp INTO emp_id, emp_sal ;
复制代码
留意:游标的查询结果会合的字段数,必须跟 INTO 背面的变量数一致,否则,在存储过程实验的时候,MySQL 会提示错误。
第四步,关闭游标
  1. CLOSE cursor_name
复制代码
有OPEN 就会有 CLOSE,也就是打开和关闭游标。使用完游标后需要关闭掉该游标。由于游标会占用系统资源,假如不及时关闭,游标会不停保持到存储过程结束,影响系统运行的效率。而关闭游标的操作,会释放游标占用的系统资源。
关闭游标之后就不能再检索查询结果中的数据行,假如需要检索只能再次打开游标。
  1. CLOSE        cur_emp;
复制代码
4.3 举例

创建存储过程“get_count_by_limit_total_salary()”,声明IN参数 limit_total_salary,DOUBLE类型;声明OUT参数total_count,INT类型。函数的功能可以实现累加薪资最高的几个员工的薪资值,直到薪资总和达到limit_total_salary参数的值,返回累加的人数给total_count。
  1. DELIMITER //
  2. CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT total_count INT)
  3. BEGIN
  4. DECLARE sum_salary DOUBLE DEFAULT 0;       
  5. #记录累加的总工资
  6. DECLARE cursor_salary DOUBLE DEFAULT 0;
  7. #记录某一个工资值
  8. DECLARE emp_count INT DEFAULT 0;
  9. #记录循环个数
  10. #定义游标
  11. DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
  12. #打开游标
  13. OPEN emp_cursor;
  14. REPEAT
  15. #使用游标(从游标中获取数据)
  16. FETCH emp_cursor INTO cursor_salary;
  17. SET sum_salary = sum_salary + cursor_salary;
  18. SET emp_count = emp_count + 1;
  19. UNTIL sum_salary >= limit_total_salary
  20. END REPEAT;
  21. SET total_count = emp_count;
  22. #关闭游标
  23. CLOSE emp_cursor;
  24. END //
  25. DELIMITER ;
复制代码
4.5 小结

游标是 MySQL 的一个重要的功能,为 逐条读取 结果会合的数据,提供了完美的办理方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简便。
但同时也会带来一些性能问题,好比在使用游标的过程中,会对数据行举行加锁 ,这样在业务并发量大的时候,不但会影响业务之间的效率,还会消耗系统资源 ,造成内存不足,这是由于游标是在内存中举行的处理。
建议:养成用完之后就关闭的习惯,这样才气进步系统的团体效率。
增补:MySQL 8.0的新特性—全局变量的持久化

在MySQL数据库中,全局变量可以通过SET GLOBAL语句来设置。比方,设置服务器语句超时的限定,可以通过设置系统变量max_execution_time来实现:
  1. SET GLOBAL MAX_EXECUTION_TIME=2000;
  2. 1
复制代码
使用SET GLOBAL语句设置的变量值只会 临时生效 。 数据库重启 后,服务器又会从MySQL设置文件中读取变量的默认值。 MySQL 8.0版本新增了 SET PERSIST 下令。比方,设置服务器的最大连接数为1000:
  1. SET PERSIST global max_connections = 1000;
  2. 1
复制代码
MySQL会将该下令的设置保存到数据目录下的 mysqld-auto.cnf 文件中,下次启动时会读取该文件,用此中的设置来覆盖默认的设置文件。
举例:
查看全局变量max_connections的值,结果如下:
  1. show variables like        '%max_connections%';
  2. /*
  3. +        ------------------------        +        -------        +
  4. | Variable_name        |        Value |
  5. +        ------------------------        +        -------        +
  6. | max_connections        |        151        |
  7. | mysqlx_max_connections        |        100        |
  8. +        ------------------------        +        -------        +*/
复制代码
设置全局变量max_connections的值:
  1. set persist max_connections=1000;
  2. #Query OK, 0 rows affected (0.00 sec)
复制代码
重启MySQL服务器 ,再次查询max_connections的值:
  1. show variables like        '%max_connections%';
  2. /*+        ------------------------        +        -------        +
  3. | Variable_name        |        Value |
  4. +        ------------------------        +        -------        +
  5. | max_connections        |        1000        |
  6. | mysqlx_max_connections        |        100        |
  7. +        ------------------------        +        -------        +*/       
复制代码
演示代码

  1. #6. 游标的使用
  2. /*
  3. 游标使用的步骤:
  4. ① 声明游标
  5. ② 打开游标
  6. ③ 使用游标(从游标中获取数据)
  7. ④ 关闭游标
  8. */
  9. #举例:创建存储过程“get_count_by_limit_total_salary()”,声明IN参数 limit_total_salary,
  10. #DOUBLE类型;声明OUT参数total_count,INT类型。函数的功能可以实现累加薪资最高的几个员工的薪资值,
  11. #直到薪资总和达到limit_total_salary参数的值,返回累加的人数给total_count。
  12. DELIMITER //
  13. CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT total_count INT)
  14. BEGIN
  15.         #声明局部变量
  16.         DECLARE sum_sal DOUBLE DEFAULT 0.0; #记录累加的工资总额
  17.         DECLARE emp_sal DOUBLE; #记录每一个员工的工资
  18.         DECLARE emp_count INT DEFAULT 0;#记录累加的人数
  19.        
  20.         #1.声明游标
  21.         DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
  22.        
  23.         #2.打开游标
  24.         OPEN emp_cursor;
  25.        
  26.         REPEAT
  27.                
  28.                 #3.使用游标
  29.                 FETCH emp_cursor INTO emp_sal;
  30.                
  31.                 SET sum_sal = sum_sal + emp_sal;
  32.                 SET emp_count = emp_count + 1;
  33.                 UNTIL sum_sal >= limit_total_salary
  34.         END REPEAT;
  35.        
  36.         SET total_count = emp_count;
  37.        
  38.         #4.关闭游标
  39.         CLOSE emp_cursor;
  40.        
  41. END //
  42. DELIMITER ;
  43. #调用
  44. CALL get_count_by_limit_total_salary(200000,@total_count);
  45. SELECT @total_count;
  46. /*输出:
  47. +--------------+
  48. | @total_count |
  49. +--------------+
  50. |           15 |
  51. +--------------+
  52. */
复制代码
课后训练

  1. #第16章_变量、流程控制与游标的课后练习
  2. /*
  3. 变量:
  4.         系统变量 (全局系统变量、会话系统变量)
  5.         用户自定义变量(会话用户变量、局部变量)
  6. */
  7. #练习1:测试变量的使用  
  8. #存储函数的练习
  9. #0. 准备工作
  10. CREATE DATABASE test16_var_cursor;
  11. USE test16_var_cursor;
  12. CREATE TABLE employees
  13. AS
  14. SELECT * FROM atguigudb.`employees`;
  15. CREATE TABLE departments
  16. AS
  17. SELECT * FROM atguigudb.`departments`;
  18. SET GLOBAL log_bin_trust_function_creators = 1;
  19. #无参有返回
  20. #1. 创建函数get_count(),返回公司的员工个数
  21. DELIMITER $
  22. CREATE FUNCTION get_count()
  23. RETURNS INT
  24. BEGIN
  25.         #声明局部变量
  26.         DECLARE emp_count INT;
  27.        
  28.         #赋值
  29.         SELECT COUNT(*) INTO emp_count FROM employees;
  30.        
  31.         RETURN emp_count;
  32. END $
  33. DELIMITER ;
  34. #调用
  35. SELECT get_count();
  36. /*输出:
  37. +-------------+
  38. | get_count() |
  39. +-------------+
  40. |         107 |
  41. +-------------+
  42. */
  43. #有参有返回
  44. #2. 创建函数ename_salary(),根据员工姓名,返回它的工资
  45. DELIMITER $
  46. CREATE FUNCTION ename_salary(emp_name VARCHAR(15))
  47. RETURNS DOUBLE
  48. BEGIN
  49.         #声明变量
  50.         SET @sal = 0; #定义了一个会话用户变量
  51.        
  52.         #赋值
  53.         SELECT salary INTO @sal FROM employees WHERE last_name = emp_name;       
  54.        
  55.         RETURN @sal;
  56. END $
  57. DELIMITER ;
  58. #调用
  59. SELECT ename_salary('Abel');
  60. /*
  61. +----------------------+
  62. | ename_salary('Abel') |
  63. +----------------------+
  64. |                11000 |
  65. +----------------------+
  66. */
  67. SELECT @sal;
  68. /*
  69. +-------+
  70. | @sal  |
  71. +-------+
  72. | 11000 |
  73. +-------+
  74. */
  75. #3. 创建函数dept_sal() ,根据部门名,返回该部门的平均工资
  76. DELIMITER //
  77. CREATE FUNCTION dept_sal(dept_name VARCHAR(15))
  78. RETURNS DOUBLE
  79. BEGIN
  80.         DECLARE avg_sal DOUBLE;
  81.        
  82.         SELECT AVG(salary) INTO avg_sal
  83.         FROM employees e JOIN departments d
  84.         ON e.department_id = d.department_id
  85.         WHERE d.department_name = dept_name;
  86.        
  87.         RETURN avg_sal;
  88. END //
  89. DELIMITER ;
  90. #调用
  91. SELECT * FROM departments;
  92. SELECT dept_sal('Marketing');
  93. /*
  94. +-----------------------+
  95. | dept_sal('Marketing') |
  96. +-----------------------+
  97. |                  9500 |
  98. +-----------------------+
  99. */
  100. #4. 创建函数add_float(),实现传入两个float,返回二者之和
  101. DELIMITER //
  102. CREATE FUNCTION add_float(value1 FLOAT,value2 FLOAT)
  103. RETURNS FLOAT
  104. BEGIN
  105.         DECLARE sum_val FLOAT ;
  106.         SET sum_val = value1 + value2;
  107.         RETURN sum_val;
  108. END //
  109. DELIMITER ;
  110. # 调用
  111. SET @v1 := 12.2;
  112. SET @v2 = 2.3;
  113. SELECT add_float(@v1,@v2);
  114. /*
  115. +--------------------+
  116. | add_float(@v1,@v2) |
  117. +--------------------+
  118. |               14.5 |
  119. +--------------------+
  120. */
  121. #2. 流程控制
  122. /*
  123. 分支:if \ case ... when \ case when ...
  124. 循环:loop \ while \ repeat
  125. 其它:leave \ iterate
  126. */
  127. #1. 创建函数test_if_case(),实现传入成绩,如果成绩>90,返回A,如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
  128. #要求:分别使用if结构和case结构实现
  129. #方式1:if
  130. DELIMITER $
  131. CREATE FUNCTION test_if_case1(score DOUBLE)
  132. RETURNS CHAR
  133. BEGIN
  134.         #声明变量
  135.         DECLARE score_level CHAR;
  136.         IF score > 90
  137.                 THEN SET score_level = 'A';
  138.         ELSEIF score > 80
  139.                 THEN SET score_level = 'B';
  140.         ELSEIF score > 60
  141.                 THEN SET score_level = 'C';
  142.         ELSE
  143.                 SET score_level = 'D';
  144.         END IF;
  145.        
  146.         #返回
  147.         RETURN score_level;
  148. END $
  149. DELIMITER ;
  150. #调用
  151. SELECT test_if_case1(56);
  152. #方式2:case when ...
  153. DELIMITER $
  154. CREATE FUNCTION test_if_case2(score DOUBLE)
  155. RETURNS CHAR
  156. BEGIN
  157.         #声明变量
  158.         DECLARE score_level CHAR;
  159.        
  160.         CASE
  161.         WHEN score > 90 THEN SET score_level = 'A';
  162.         WHEN score > 80 THEN SET score_level = 'B';
  163.         WHEN score > 60 THEN SET score_level = 'C';
  164.         ELSE SET score_level = 'D';
  165.         END CASE;
  166.        
  167.         #返回
  168.         RETURN score_level;
  169. END $
  170. DELIMITER ;
  171. #调用
  172. SELECT test_if_case2(76);
  173. #2. 创建存储过程test_if_pro(),传入工资值,如果工资值<3000,则删除工资为此值的员工,
  174. # 如果3000 <= 工资值 <= 5000,则修改此工资值的员工薪资涨1000,否则涨工资500
  175. DELIMITER $
  176. CREATE PROCEDURE test_if_pro(IN sal DOUBLE)
  177. BEGIN
  178.         IF sal < 3000
  179.                 THEN DELETE FROM employees WHERE salary = sal;
  180.         ELSEIF sal <= 5000
  181.                 THEN UPDATE employees SET salary = salary + 1000 WHERE salary = sal;
  182.         ELSE
  183.                 UPDATE employees SET salary = salary + 500 WHERE salary = sal;
  184.         END IF;
  185. END $
  186. DELIMITER ;
  187. #调用
  188. CALL test_if_pro(24000);
  189. SELECT * FROM employees;
  190. #3. 创建存储过程insert_data(),传入参数为 IN 的 INT 类型变量 insert_count,实现向admin表中
  191. #批量插入insert_count条记录
  192. CREATE TABLE admin(
  193. id INT PRIMARY KEY AUTO_INCREMENT,
  194. user_name VARCHAR(25) NOT NULL,
  195. user_pwd VARCHAR(35) NOT NULL
  196. );
  197. SELECT * FROM admin;
  198. DELIMITER $
  199. CREATE PROCEDURE insert_data(IN insert_count INT)
  200. BEGIN
  201.         #声明变量
  202.         DECLARE init_count INT DEFAULT 1; #①初始化条件
  203.        
  204.         WHILE init_count <= insert_count DO #② 循环条件
  205.                 #③ 循环体
  206.                 INSERT INTO admin(user_name,user_pwd) VALUES (CONCAT('atguigu-',init_count),ROUND(RAND()*1000000));
  207.                 #④ 迭代条件
  208.                 SET init_count = init_count + 1;
  209.         END WHILE;
  210. END $
  211. DELIMITER ;
  212. #调用
  213. CALL insert_data(100);
  214. #3. 游标的使用
  215. #创建存储过程update_salary(),参数1为 IN 的INT型变量dept_id,表示部门id;
  216. #参数2为 IN的INT型变量change_sal_count,表示要调整薪资的员工个数。查询指定id部门的员工信息,
  217. #按照salary升序排列,根据hire_date的情况,调整前change_sal_count个员工的薪资,详情如下。
  218. DELIMITER $
  219. CREATE PROCEDURE update_salary(IN dept_id INT,IN change_sal_count INT)
  220. BEGIN
  221.         #声明变量
  222.         DECLARE emp_id INT ;#记录员工id
  223.         DECLARE emp_hire_date DATE; #记录员工入职时间
  224.        
  225.         DECLARE init_count INT DEFAULT 1; #用于表示循环结构的初始化条件
  226.         DECLARE add_sal_rate DOUBLE ; #记录涨薪的比例
  227.        
  228.         #声明游标
  229.         DECLARE emp_cursor CURSOR FOR SELECT employee_id,hire_date FROM employees
  230.         WHERE department_id = dept_id ORDER BY salary ASC;
  231.        
  232.         #打开游标
  233.         OPEN emp_cursor;
  234.        
  235.         WHILE init_count <= change_sal_count DO
  236.                 #使用游标
  237.                 FETCH emp_cursor INTO emp_id,emp_hire_date;
  238.                
  239.                 #获取涨薪的比例
  240.                 IF (YEAR(emp_hire_date) < 1995)
  241.                         THEN SET add_sal_rate = 1.2;
  242.                 ELSEIF(YEAR(emp_hire_date) <= 1998)
  243.                         THEN SET add_sal_rate = 1.15;
  244.                 ELSEIF(YEAR(emp_hire_date) <= 2001)
  245.                         THEN SET add_sal_rate = 1.10;
  246.                 ELSE
  247.                         SET add_sal_rate = 1.05;
  248.                 END IF;
  249.                
  250.                 #涨薪操作
  251.                 UPDATE employees SET salary = salary * add_sal_rate
  252.                 WHERE employee_id = emp_id;
  253.                
  254.                 #迭代条件的更新
  255.                 SET init_count = init_count + 1;
  256.        
  257.         END WHILE;
  258.        
  259.         #关闭游标
  260.         CLOSE emp_cursor;
  261. END $
  262. DELIMITER ;
  263. #调用
  264. CALL update_salary(50,3);
  265. SELECT employee_id,hire_date,salary
  266. FROM employees
  267. WHERE department_id = 50
  268. ORDER BY salary ASC;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4