【⑰MySQL】 变量 | 循环 | 游标 | 处理程序
前言
✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL变量 | 循环 | 游标 | 处理程序的分享✨
目录
- 前言
- 1. 变量
- 1.1系统变量
- 1.2 用户变量
- 2. 定义条件与处理程序
- 2.1 案例分析
- 2.2 定义条件
- 2.3 定义处理程序
- 2.4 案例解决
- 3. 流程控制
- 3.1 分支结构
- 3.2 循环结构
- 3.3 跳转语句
- 4. 游标
- 4.1 什么是游标(或光标)
- 4.2 使用游标步骤
- 4.3 举例
- 4.4 小结
1. 变量
在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。
在 MySQL 数据库中,变量分为 系统变量以及用户自定义变量。
1.1系统变量
1.1.1系统变量分类
变量由系统定义,不是用户定义,属于服务器层面。启动MySQL服务,生成MySQL服务实例期间, MySQL将为MySQL服务器内存中的系统变量赋值,这些系统变量定义了当前MySQL服务实例的属性、特征。这些系统变量的值要么是 编译MySQL时参数 的默认值,要么是 配置文件 (例如my.ini等)中的参数值。大家可以在官网Server System Variables中查看MySQL文档的系统变量。
系统变量分为全局系统变量(需要添加global关键字)以及会话系统变量(需要添加session关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。**如果不写,默认是会话级别。**静态变量(在 MySQL 服务实例运行期间它们的值不能使用 set 动态修改)属于特殊的全局系统变量。
每一个MySQL客户机成功连接MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例 会在MySQL服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制。如下图:

- 全局系统变量针对于所有会话(连接)有效,但不能跨重启。
- 会话系统变量仅针对于当前会话(连接)有效。会话期间,当前会话对某个会话系统变量值的修改,不会影响其他会话同一个会话系统变量的值。
- 会话1对某个全局系统变量值的修改会导致会话2中同一个全局系统变量值的修改。
在MySQL中有些系统变量只能是全局的,例如 max_connections 用于限制服务器的最大连接数;有些系统变量作用域既可以是全局又可以是会话,例如 character_set_client 用于设置客户端的字符集;有些系统变量的作用域只能是当前会话,例如 pseudo_thread_id 用于标记当前会话的 MySQL 连接 ID。
1.1.2查看系统变量
-
查看所有或部分系统变量
#查看所有全局变量 SHOW GLOBAL VARIABLES;#查看所有会话变量 SHOW SESSION VARIABLES; #或 SHOW VARIABLES;#查看满足条件得部分系统变量 SHOW GLOBAL VARIABLES LIKE '%标识符%';#查看满足条件的部分会话变量 SHOW SESSION VARIABLES LIKE '%标识符%'范例:
SHOW GLOBAL VARIABLES LIKE 'admin_%'; -
查看指定系统变量
作为 MySQL 编码规范,MySQL 中的系统变量以 两个“@” 开头,其中“@@global”仅用于标记全局系统变量,“@@session”仅用于标记会话系统变量。“@@”首先标记会话系统变量,如果会话系统变量不存在, 则标记全局系统变量。
#查看指定的系统变量的值
SELECT @@global.变量名;#查看指定的会话变量的值
SELECT @@session.变量名;
#或
SELECT @@变量名;
- 修改系统变量的值
有些时候,数据库管理员需要修改系统变量的默认值,以便修改当前会话或者MySQL服务实例的属性、 特征。具体方法:
方式1:修改MySQL 配置文件 ,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)
方式2:在MySQL服务运行期间,使用“set”命令重新设置系统变量的值
#为某个系统变量赋值
#方式1:
SET @@global.变量名=值;
#方式2:
SET GLOBAL 变量名=值;#为某个会话变量赋值
#方式1:
SET @@session.变量名=值;
#方式2:
SET SESSION 变量名=值
范例:
SELECT @@global.autocommit;
SET GLOBAL autocommit=0;
SELECT @@session.use_secondary_engine;
SET @@session.use_secondary_engine='OFF';
SET GLOBAL max_connections = 1000;
SELECT @@global.max_connections;
1.1.3 补充:MySQL 8.0的新特性——全局变量的持久化
在MySQL数据库中,全局变量可以通过SET GLOBAL语句来设置。例如,设置服务器语句超时的限制,可以通过设置系统变量max_execution_time来实现:
SET GLOBAL MAX_EXECUTION_TIME=2000;
使用SET GLOBAL语句设置的变量值只会 临时生效 。 数据库重启 后,服务器又会从MySQL配置文件中读取变量的默认值。 MySQL 8.0版本新增了 SET PERSIST 命令。例如,设置服务器的最大连接数为1000:
SET PERSIST global max_connections = 1000;
MySQL会将该命令的配置保存到数据目录下的 mysqld-auto.cnf 文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件。
举例:
查看全局变量max_connections的值,结果如下:
mysql> show variables like '%max_connections%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| max_connections | 151 |
| mysqlx_max_connections | 100|
+------------------------+-------+
2 rows in set, 1 warning (0.00 sec)
设置全局变量max_connections的值:
mysql> set persist max_connections=1000;
Query OK, 0 rows affected (0.00 sec)
重启MySQL服务器 ,再次查询max_connections的值:
mysql> show variables like '%max_connections%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| max_connections | 1000 |
| mysqlx_max_connections | 100 |
+------------------------+-------+
2 rows in set, 1 warning (0.00 sec)
1.2 用户变量
1.2.1 用户变量分类
用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以 一个“@” 开头。根据作用范围不同,又分为会话用户变量和局部变量。
- 会话用户变量:作用域和会话变量一样,只对当前连接会话有效。
- 局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数中使用。
注意:在 MySQL 5.0以前的版本中是大小写敏感的,因此要注意(在 MySQL 5.0以后已经不区分大小写了)。
1.2.2 会话用户变量
- 变量的定义
#方式1:"= 或 ":=
SET @用户变量=值
SET @用户变量:=值#方式2:":=" 或 INTO关键字
SELECT @用户变量 :=表达式[FROM等子句];
SELECT 表达式 INTO @用户变量 [FROM等子句];
- 查看自定义变量的值
SELECT @用户变量;
范例:
SET @n1 =1;
SET @n2 :=3;
SET @sum := @n1+@n2;
SELECT @sum;
SELECT @num := COUNT(*) FROM emps;
SELECT @num;
SELECT AVG(sal) INTO @avgsal FROM emps;
SELECT @avgsal;
#查看某个未定义的变量时,将得到NULL值
SELECT @maye;
1.2.3 局部变量
定义:可以使用 DECLARE 语句定义一个局部变量作用域:仅仅在定义它的 BEGIN … END 中有效
位置:只能放在 BEGIN … END 中,而且只能放在第一句
BEGIN#声明局部变量DECLARE 变量1 数据类型 [DEFAULT 默认值];DECLARE 变量2,变量3,... 数据类型 [DEFAULT 默认值];#为局部变量赋值SET 变量1 = 值;SELECT 字段 INTO 变量2 [FROM 子句];#查看局部变量的值SELECT 变量1,变量2,变量3;
END
1. 定义变量
DECLARE 变量名 类型 [DEFAULT 值]; # 如果没有DEFAULT子句,初始值为NULL
范例:
DECLARE num INT DEFAULT 100;
2.变量赋值
方式1:一般用于赋简单的值
SET 变量=值;
SET 变量:=值;
方式2:一般用于赋表中的字段值
SELECT 字段名或表达式 INTO 变量名 FROM 表;
3.使用变量
SELECT 局部变量名;
范例1:声明局部变量,并分别赋值为emps表中empno为7369的ename和sal。
CREATE PROCEDURE set_value()
BEGINDECLARE emp_name VARCHAR(25); DECLARE em_sal DOUBLE(10,2);SELECT ename,sal INTO emp_name,emp_sal FROM empsWHERE empno = 7369;SELECT emp_name,emp_sal;
END;
范例2:声明两个变量,求和并打印 (分别使用会话用户变量、局部变量的方式实现)
#方式1:使用用户变量
SET @m=1;
SET @n=2;
SET @sum = @m+@n;
SELECT @sum;
#方式2:使用局部变量
CREATE PROCEDURE add_value()
BEGINDECLARE m INT DEFAULT 1;DECLARE n INT DEFAULT 3;DECLARE sum INT;SET sum=m+n;SELECT sum;
END;
范例3:创建存储过程“diff_sal”查询某员工和他领导的薪资差距,并用IN参数eno接收员工编号,用OUT参数dif_sal输出薪资差距结果。
CREATE PROCEDURE diff_sal(IN eno INT,OUT dif_sal DOUBLE)
BEGINDECLARE mgr_eno INT;DECLARE mgr_sal DOUBLE(10,2);DECLARE eno_sal DOUBLE(10,2);SELECT sal,mgr INTO eno_sal,mgr_eno FROM emp WHERE empno=eno;SELECT sal INTO mgr_sal FROM emp WHERE empno=mgr_eno;SET dif_sal=mgr_sal-eno_sal;
END;
CALL diff_sal(7566,@dif_sal);
SELECT @dif_sal;
1.2.4 会话用户变量与局部变量对比
| 变量类型 | 作用域 | 定义位置 | 语法 |
|---|---|---|---|
| 会话用户变量 | 作用域当前会话 | 定义位置会话的任何地方 | 语法加@符号,不用指定类型 |
| 局部变量 | 定义它的BEGIN END中 | BEGIN END的第一句话 | 一般不用加@,需要指定类型 |
2. 定义条件与处理程序
定义条件是事先定义程序执行过程中可能遇到的问题, 处理程序定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。
说明:定义条件和处理程序在存储过程、存储函数中都是支持的。
2.1 案例分析
案例分析:创建一个名称为“UpdateData”的存储过程。代码如下:
CREATE PROCEDURE UpdateData()
BEGINSET @x = 1;UPDATE emps SET sal = NULL WHERE ename = 'WARD'; SET @x = 2;UPDATE emps SET sal = 200 WHERE ename = 'WARD'; SET @x = 3;
END ;
调用存储过程:
mysql> CALL UpdateData();
ERROR 1048 (23000): Column 'sal' cannot be nullmysql> SELECT @x;
+------+
| @x |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
可以看到,此时@x变量的值为1。结合创建存储过程的SQL语句代码可以得出:在存储过程中未定义条件 和处理程序,且当存储过程中执行的SQL语句报错时,MySQL数据库会抛出错误,并退出当前SQL逻辑, 不再向下继续执行。
2.2 定义条件
定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个错误名字和错误条件关联起来。这个名字可以随后被用在定义处理程序的 DECLARE HANDLER 语句中。
定义条件使用DECLARE语句,语法格式如下:
DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
错误码的说明:
-
MySQL_error_code和sqlstate_value都可以表示MySQL的错误。- MySQL_error_code是数值类型错误代码。
- sqlstate_value是长度为5的字符串类型错误代码。
-
例如,在ERROR 1048 (23000)中,1048是MySQL_error_code,'23000’是sqlstate_value。
-
例如,在ERROR 1146(42S02)中,1146是MySQL_error_code,'42S02’是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';
2.3 定义处理程序
可以为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';
2.4 案例解决
在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为1048时,执行
CONTINUE操作,并且将@proc_value的值设置为-1。
CREATE PROCEDURE UpdateDataNoCondition()
BEGIN#定义处理程序DECLARE CONTINUE HANDLER FOR 1048 SET @proc_value = -1;SET @x = 1;UPDATE emps SET sal = NULL WHERE ename = 'WARD'; SET @x = 2;UPDATE emps SET sal = 200 WHERE ename = 'WARD'; SET @x = 3;
END ;
调用过程:
mysql> CALL UpdateDataWithCondition(); Query OK, 0 rows affected (0.01 sec)mysql> SELECT @x,@proc_value;
+------+-------------+
| @x | @proc_value |
+------+-------------+
| 3 | -1 |
+------+-------------+
1 row in set (0.00 sec)
范例:创建一个名称为“InsertDataWithCondition”的存储过程,代码如下。
在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到sqlstate_value值为23000时,执行EXIT操 作,并且将@proc_value的值设置为-1。
#准备工作
CREATE TABLE depts
AS
SELECT * FROM test.dept;ALTER TABLE depts
ADD CONSTRAINT uk_dept_name UNIQUE(deptno);
CREATE PROCEDURE InsertDataWithCondition()
BEGINDECLARE duplicate_entry CONDITION FOR SQLSTATE '23000' ; DECLARE EXIT HANDLER FOR duplicate_entry SET @proc_value = -1;SET @x = 1;INSERT INTO depts(dname) VALUES('测试'); SET @x = 2;INSERT INTO depts(dname) VALUES('测试');SET @x = 3;
END ;
调用存储过程:
mysql> CALL InsertDataWithCondition(); Query OK, 0 rows affected (0.01 sec)mysql> SELECT @x,@proc_value;
+------+-------------+
| @x | @proc_value |
+------+-------------+
| 2 | -1 |
+------+-------------+
1 row in set (0.00 sec)
3. 流程控制
解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:
- 顺序结构 :程序从上往下依次执行
- 分支结构 :程序按条件进行选择执行,从两条或多条路径中选择一条执行
- 循环结构 :程序满足一定条件下,重复执行一组语句
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。
- 条件判断语句 :IF 语句和 CASE 语句
- 循环语句 :LOOP、WHILE 和 REPEAT 语句
- 跳转语句 :ITERATE 和 LEAVE 语句
3.1 分支结构
3.1.1 分支结构之 IF
IF 语句的语法结构是:
IF 表达式1 THEN 操作1
[ELSEIF 表达式2 THEN 操作2]
...
[ELSE 操作N]
END IF
根据表达式的结果为TRUE或FALSE执行相应的语句。这里“[]”中的内容是可选的。
-
特点:① 不同的表达式对应不同的操作
② 使用在begin end中 -
范例1:
IF val IS NULL
THEN SELECT 'val is null';
ELSE SELECT 'val is not null';
END IF
范例2:声明存储过程“update_sal_by_eno”,定义IN参数eno,输入员工编号。判断该员工 薪资如果低于2000元并且入职时间超过5年,就涨薪500元;否则就不变。
CREATE PROCEDURE update_sal_by_eno(IN eno INT)
BEGINDECLARE emp_sal DOUBLE;DECLARE emp_hir DOUBLE;SELECT sal,DATEDIFF(CURDATE(),hiredate)/365 INTO emp_sal,emp_hir FROM emp WHERE empno=eno;IF emp_sal<2000 AND emp_hir > 5THEN UPDATE emp SET sal=sal+500 WHERE empno=eno;END IF;
END;
CALL update_sal_by_eno(7369);
SELECT * FROM emp WHERE empno=7369;
范例3:声明存储过程“update_sal_by_eno2”,定义IN参数eno,输入员工编号。判断该员工 薪资如果低于3000元,就更新薪资为3000元;薪资如果大于等于3000元且低于5000的,但是奖金 比例为NULL的,就更新奖金比例为0.01;其他的涨薪100元。
CREATE PROCEDURE update_sal_by_eno2(IN eno INT)
BEGINDECLARE emp_sal DOUBLE;DECLARE emp_com DOUBLE;SELECT sal,comm INTO emp_sal,emp_com FROM emp WHERE empno=eno;IF emp_sal<3000THEN UPDATE emp SET sal=3000 WHERE empno=eno;ELSEIF emp_sal<5000 AND emp_com IS NULLTHEN UPDATE emp SET comm=0.01*sal WHERE empno=eno;ELSE UPDATE emp SET sal=sal+800 WHERE empno=eno;END IF;
END;
CALL update_sal_by_eno2(7698);
SELECT * FROM emp WHERE empno=7698;
3.1.2 分支结构之 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后面不需要)
范例1:使用CASE流程控制语句的第1种格式,判断val值等于1、等于2,或者两者都不等。
CASE valWHEN 1 THEN SELECT 'val is 1'; WHEN 2 THEN SELECT 'val is 2';ELSE SELECT 'val is not 1 or 2';
END CASE;
范例2:使用CASE流程控制语句的第2种格式,判断val是否为空、小于0、大于0或者等于0。
CASEWHEN val IS NULL THEN SELECT 'val is null'; WHEN val < 0 THEN SELECT 'val is less than 0';WHEN val > 0 THEN SELECT 'val is greater than 0'; ELSE SELECT 'val is 0';
END CASE;
范例3:声明存储过程“update_salary_by_eno4”,定义IN参数eno,输入员工编号。判断改员工所属部门,如果在10号部门,工资加500;如果在20号部门,工资加600;如果在30号部门,工资加700;其他部门加300。
CREATE PROCEDURE update_salary_by_eno4(IN eno INT)
BEGINDECLARE emp_deptno INT;DECLARE emp_sal DOUBLE;SELECT deptno,sal INTO emp_deptno,emp_sal FROM emp WHERE empno=eno;CASE emp_deptnoWHEN 10 THEN UPDATE emp SET sal=emp_sal+500 WHERE empno=eno;WHEN 20 THEN UPDATE emp SET sal=emp_sal+600 WHERE empno=eno;WHEN 30 THEN UPDATE emp SET sal=emp_sal+700 WHERE empno=eno;ELSE UPDATE emp SET sal=emp_sal+300 WHERE empno=eno;
END CASE;
END;
CALL update_salary_by_eno4(7698);
SELECT * FROM emp WHERE empno=7698;
范例4:声明存储过程pro_sal_grade,定义IN参数eno,输入员工编号。判断该员工的薪资等级,如果在[700,1200]之间,为等级一;如果在[1201,1400]之间,为等级二;如果在[1401,2000]之间,为等级三;如果在[2001,3000]之间,为等级四;如果在[3001,9999]之间,为等级五;
CREATE PROCEDURE sal_grade(IN eno INT)
BEGINDECLARE emp_sal INT; SELECT sal INTO emp_sal FROM emps WHERE empno = eno;CASE WHEN emp_sal>=700 AND emp_sal<=1200THEN SELECT '等级一'; WHEN emp_sal>=1201 AND emp_sal<=1400 THEN SELECT '等级二'; WHEN emp_sal>=1401 AND emp_sal<=2000 THEN SELECT '等级三';WHEN emp_sal>=2001 AND emp_sal<=3000 THEN SELECT '等级四';WHEN emp_sal>=3001 AND emp_sal<=999 THEN SELECT '等级五';END CASE;
END //
3.2 循环结构
3.2.1 循环结构之 LOOP
LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子 句),跳出循环过程。
LOOP语句的基本格式如下:
[loop_label:] LOOP循环执行的语句
END LOOP [loop_label]
其中,loop_label表示LOOP语句的标注名称,该参数可以省略。
范例1:使用LOOP语句进行循环操作,id值小于10时将重复执行循环过程。
DECLARE id INT DEFAULT 0;
add_loop:LOOPSET id = id +1;IF id >= 10 THEN LEAVE add_loop; END IF;END LOOP add_loop;
范例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程“update_sal_loop()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家涨薪,薪资涨为原来的1.1倍。直到全公司的平均薪资达到8000结束。并统计循环次数。
CREATE PROCEDURE update_sal_loop(OUT num INT)
BEGINDECLARE avg_sal DOUBLE; DECLARE loop_count INT DEFAULT 0;SELECT AVG(sal) INTO avg_sal FROM emps;label_loop:LOOPIF avg_sal >= 8000 THEN LEAVE label_loop; END IF;UPDATE emps SET sal = sal * 1.1; SET loop_count = loop_count + 1;SELECT AVG(sal) INTO avg_salary FROM emps; END LOOP label_loop;SET num = loop_count;
END ;
3.2.2 循环结构之 WHILE
WHILE语句创建一个带条件判断的循环过程。WHILE在执行语句执行时,先对指定的表达式进行判断,如 果为真,就执行循环内的语句,否则退出循环。WHILE语句的基本格式如下:
[while_label:] WHILE 循环条件 DO循环体
END WHILE [while_label];
while_label为WHILE语句的标注名称;如果循环条件结果为真,WHILE语句内的语句或语句群被执行,直 至循环条件为假,退出循环。
范例1:WHILE语句示例,i值小于10时,将重复执行循环过程,代码如下:
CREATE PROCEDURE test_while()
BEGINDECLARE i INT DEFAULT 0;WHILE i < 10 DO SET i = i + 1;END WHILE;SELECT i;
END ;
#调用
CALL test_while();
范例2:市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。声明存储过程“update_salary_while()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家降薪,薪资降为原来的90%。直到全公司的平均薪资达到3000结束。并统计循环次数。
CREATE PROCEDURE update_sal_while(OUT num INT)
BEGINDECLARE avg_sal DOUBLE ;DECLARE while_count INT DEFAULT 0;SELECT AVG(sal) INTO avg_sal FROM emps; WHILE avg_sal > 3000 DOUPDATE emps SET sal = sal * 0.9; SET while_count = while_count + 1;SELECT AVG(sal) INTO avg_sal FROM emps; END WHILE;SET num = while_count;
END //
3.2.3 循环结构之 REPEAT
REPEAT语句创建一个带条件判断的循环过程。与WHILE循环不同的是,REPEAT 循环首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止。
REPEAT语句的基本格式如下:
[repeat_label:] REPEAT循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]
repeat_label为REPEAT语句的标注名称,该参数可以省略;REPEAT语句内的语句或语句群被重复,直至expr_condition为真。
范例1:
CREATE PROCEDURE test_repeat()
BEGINDECLARE i INT DEFAULT 0;REPEATSET i = i + 1; UNTIL i >= 10END REPEAT;SELECT i;
END ;
范例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程“update_salary_repeat()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家涨薪,薪资涨为原来的1.15倍。直到全公司的平均薪资达到9000结束。并统计循环次数。
CREATE PROCEDURE update_salary_repeat(OUT num INT)
BEGINDECLARE avg_sal DOUBLE ;DECLARE repeat_count INT DEFAULT 0;SELECT AVG(sal) INTO avg_sal FROM emps; REPEATUPDATE emps SET sal = sal * 1.15;SET repeat_count = repeat_count + 1;SELECT AVG(sal) INTO avg_sal FROM emps; UNTIL avg_sal >= 9000END REPEAT;SET num = repeat_count;
END //
对比三种循环结构:
1、这三种循环都可以省略名称,但如果循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名称。
2、 LOOP:一般用于实现简单的"死"循环 WHILE:先判断后执行 REPEAT:先执行后判断,无条件至少执行一次
3.3 跳转语句
3.3.1 跳转语句之 LEAVE语句
LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出程序体的操作。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 break。
基本格式如下:
LEAVE 标记名
其中,label参数表示循环的标志。LEAVE和BEGIN … END或循环一起被使用。
范例1:创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN…END加标记名,并在BEGIN…END中使用IF语句判断num参数的值。
- 如果num<=0,则使用LEAVE语句退出BEGIN…END;
- 如果num=1,则查询“emps”表的平均薪资;
- 如果num=2,则查询“emps”表的最低薪资;
- 如果num>2,则查询“emps”表的最高薪资。
IF语句结束后查询“emp”表的总人数。
CREATE PROCEDURE leave_begin(IN num INT) begin_label: BEGINIF num<=0THEN LEAVE begin_label; ELSEIF num=1THEN SELECT AVG(sal) FROM emps; ELSEIF num=2THEN SELECT MIN(sal) FROM emps; ELSESELECT MAX(sal) FROM emps; END IF;SELECT COUNT(*) FROM emps;
END ;
范例2:当市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。声明存储过程“leave_while()”,声明OUT参数num,输出循环次数,存储过程中使用WHILE循环给大家降低薪资为原来薪资的90%,直到全公 司的平均薪资小于等于10000,并统计循环次数。
CREATE PROCEDURE leave_while(OUT num INT)
BEGIN#DECLARE avg_sal DOUBLE;#记录平均工资DECLARE while_count INT DEFAULT 0; #记录循环次数SELECT AVG(sal) INTO avg_sal FROM emps; #① 初始化条件while_label:WHILE TRUE DO #② 循环条件#③ 循环体IF avg_sal <= 10000 THEN LEAVE while_label;END IF;UPDATE emps SET sal = sal * 0.9; SET while_count = while_count + 1;#④ 迭代条件SELECT AVG(sal) INTO avg_sal FROM emps; END WHILE;#赋值SET num = while_count;
END ;
3.3.2 跳转语句之 ITERATE 语句
ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序 转到语句段开头处。如果你有面向过程的编程语言的使用经验,你可以把 ITERATE 理解为 continue,意思为“再次循环”。
语句基本格式如下:
ITERATE label
label参数表示循环的标志。ITERATE语句必须跟在循环标志前面。
范例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作。
-
如果num < 10,则继续执行循环;
-
如果num > 15,则退出循环结构;
CREATE PROCEDURE test_iterate()
BEGINDECLARE num INT DEFAULT 0;my_loop:LOOPSET num = num + 1;IF num < 10THEN ITERATE my_loop; ELSEIF num > 15THEN LEAVE my_loop; END IF;SELECT '顿开教育:让每个学员都学有所成'; END LOOP my_loop;
END //
4. 游标
4.1 什么是游标(或光标)
虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录, 但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一条记录 ,并对记录的数据进行处理。
这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录 进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过程开发的能力。
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用 ,我们可以通过操作游标来对数据行进行操作。
MySQL中游标可以在存储过程和函数中使用。
比如,我们查询了 emps数据表中工资高于1500的员工都有哪些:
SELECT empno,ename,sal FROM emps WHERE sal > 1500;

这里我们就可以通过游标来操作数据行,如图所示此时游标所在的行是“7698”的记录,我们也可以在结果集上滚动游标,指向结果集中的任意一行。
4.2 使用游标步骤
游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前被声明。 如果我们想要使用游标,一般需要经历四个步骤。不同的 DBMS 中,使用游标的语法可能略有不同。
第一步,声明游标
在MySQL中,使用DECLARE关键字来声明游标,其语法的基本形式如下:
DECLARE cursor_name CURSOR FOR select_statement;
这个语法适用于 MySQL,SQL Server,DB2 和 MariaDB。如果是用 Oracle 或者 PostgreSQL,需要写成:
DECLARE cursor_name CURSOR IS select_statement;
要使用 SELECT 语句来获取数据结果集,而此时还没有开始遍历数据,这里 select_statement 代表的是
SELECT 语句,返回一个用于创建游标的结果集。
比如:
DECLARE cur_emp CURSOR FOR
SELECT empno,sal FROM emps;
DECLARE cursor_dept CURSOR FOR
SELECT deptno,dname, loc FROM depts ;
第二步,打开游标
打开游标的语法如下:
OPEN cursor_name
当我们定义好游标之后,如果想要使用游标,必须先打开游标。打开游标的时候 SELECT 语句的查询结果集就会送到游标工作区,为后面游标的 逐条读取 结果集中的记录做准备。
OPEN cur_emp ;
第三步,使用游标(从游标中取得数据)
语法如下:
FETCH cursor_name INTO var_name [, var_name] ...
这句的作用是使用 cursor_name 这个游标来读取当前行,并且将数据保存到 var_name 这个变量中,游标指针指到下一行。如果游标读取的数据行有多个列名,则在 INTO 关键字后面赋值给多个变量名即可。
注意:var_name必须在声明游标之前就定义好。
FETCH cur_emp INTO emp_no, emp_sal ;
注意:游标的查询结果集中的字段数,必须跟 INTO 后面的变量数一致,否则,在存储过程执行的时候,MySQL 会提示错误。
第四步,关闭游标
CLOSE cursor_name
有 OPEN 就会有 CLOSE,也就是打开和关闭游标。当我们使用完游标后需要关闭掉该游标。因为游标会占用系统资源 ,如果不及时关闭,游标会一直保持到存储过程结束,影响系统运行的效率。而关闭游标的操作,会释放游标占用的系统资源。
关闭游标之后,我们就不能再检索查询结果中的数据行,如果需要检索只能再次打开游标。
CLOSE cur_emp;
4.3 举例
创建存储过程“get_count_by_limit_total_salary()”,声明IN参数 limit_total_salary;声明OUT参数total_count。函数的功能可以实现累加薪资最高的几个员工的薪资值,直到薪资总和 达到limit_total_salary参数的值,返回累加的人数给total_count。
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_sal INT,OUT total_count INT)
BEGIN#声明变量DECLARE sum_sal INT DEFAULT 0; #记录累加的总工资DECLARE emp_count INT DEFAULT 0; #记录循环总次数DECLARE emp_sal INT; #当前员工的工资#声明游标DECLARE cursor_emp CURSOR FOR SELECT sal FROM emp ORDER BY sal DESC;#打开游标OPEN cursor_emp;#使用游标WHILE sum_sal < limit_total_sal DOFETCH cursor_emp INTO emp_sal;SET sum_sal = emp_sal+sum_sal;SET emp_count = emp_count+1;END WHILE;#关闭游标CLOSE cursor_emp;#设置传出参数值SET total_count = emp_count;
END;
4.4 小结
游标是 MySQL 的一个重要的功能,为 逐条读取 结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。
但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁 ,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会 消耗系统资源 ,造成内存不足,这是因为游标是在内存中进行的处理。
建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。
相关文章:
【⑰MySQL】 变量 | 循环 | 游标 | 处理程序
前言 ✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL变量 | 循环 | 游标 | 处理程序的分享✨ 目录 前言1. 变量1.1系统变量1.2 用户变量 2. 定义条件与处理程序2.1 案例分析2.2 定义条件2.3 定义处理程序2.4 案例解决 3. 流程控制3.1 分支结构3.2 循环结构3.3 跳转…...
如何在arXiv上发表一篇文章
目录 1. 初始信息确认2. 提交论文文件3. 论文编译结果4. 补充论文信息5. 总览 1. 初始信息确认 版权问题需要根据个人情况选择。 IEEE, Elsevier, BioMed Central, 这几个出版商都允许在投稿之前挂文章到arXiv下。通常是选择: arXiv.org perpetual, non-exclusive l…...
重要性采样
重要性采样 前言 离散型随机变量 X X X,我们可以通过以下方法求取其期望: 直接计算法,需要知道概率分布: E ( X ) ∑ x ∈ X [ p ( x ) ⋅ x ] \mathbb{E}(X)\sum_{x\in X}\left[p(x)\cdot x\right] E(X)x∈X∑[p(x)⋅x] 采…...
说说Omega架构
分析&回答 Omega架构我们暂且称之为混合数仓。 什么是ECS设计模式 在谈我们的解法的时候,必须要先提ECS的设计模式。 简单的说,Entity、Component、System分别代表了三类模型。 实体(Entity):实体是一个普通的对象。通常,…...
高忆管理:光刻胶概念强势拉升,同益股份、格林达涨停
光刻胶概念5日盘中强势拉升,截至发稿,同益股份、格林达涨停,波长光电、晶瑞电材涨超7%,容大感光涨逾5%,华懋科技、茂莱光学、苏大维格、南大光电等均走强。 音讯面上,据新加坡《联合早报》网站9月2日报导&…...
计算机图形学线性代数相关概念
Transformation(2D-Model) Scale(缩放) [ x ′ y ′ ] [ s 0 0 s ] [ x y ] (等比例缩放) \left[ \begin{matrix} x \\ y \end{matrix} \right] \left[ \begin{matrix} s & 0 \\ 0 & s \end{matrix} \right] \left[ \begin{matrix} x \\ y \en…...
开源PHP 代挂机源码,可对接QQ、网易云、哔哩哔哩、QQ空间、等级加速等等
本程序运行环境PHP5.6 95dg/config.php修改系统数据库 进入数据库绑定 你搭建的域名即可 部署完成 进入数据库 找到data 输入绑定授权域名即可进行授权打开此网站 网站是无对接接口 需要您自行找对接接口即可 本源码有点乱 有实力的铁铁 可以修改一下哦!...
【仿牛客论坛java项目】第五章 Kafka,构建TB级异步消息系统:阻塞队列、Kafka入门、Spring整合Kafka、发送系统通知、显示系统通知
这里写自定义目录标题 一、阻塞队列简单的阻塞队列测试案例总结阻塞队列 二、Kafka入门1、基础知识Kafka术语消息队列实现方式两种 2、配置3、启动全部命令启动 zookeeper 服务器再启动 kafka 服务器创建Topic关闭 4、总结Kafka的特点Kafka的术语 三、 Spring整合Kafka导入依赖…...
【AIGC专题】Stable Diffusion 从入门到企业级实战0401
一、概述 本章是《Stable Diffusion 从入门到企业级实战》系列的第四部分能力进阶篇《Stable Diffusion ControlNet v1.1 图像精准控制》第01节, 利用Stable Diffusion ControlNet Inpaint模型精准控制图像生成。本部分内容,位于整个Stable Diffusion生…...
Matlab信号处理1:模拟去除信号噪声
由于工作内容涉及信号系统、信号处理相关知识,本人本硕均为计算机相关专业,专业、研究方向均未涉及信号相关知识,因此需进行系统地学习。之前已将《信号与系统》快速过了一遍,但感觉较抽象且理解较浅显。在此系统地学习如何使用Ma…...
Bootstrap的行、列布局设计(网络系统设计)
目录 00-基础知识01-等宽列布局02-指定某一列的宽度03-根据内容自动改变列的宽度04-五种预定义列宽度 .col、.col-sm-*、.col-md-*、.col-lg-*、.col-xl-*05-不同视口宽度按不同的分列方案划分06-删除列内容的盒模型的外边距07-超过12列怎么办?08-重新排列各列的顺序…...
1.1 计算机网络在信息时代中的作用
思维导图: 正文: 我的理解: 这段话是一本书或课程的第一章简介,它的目的是为读者或学生提供一个关于计算机网络基础知识的框架或大纲。 首先,它强调了这章是整本书的一个概览,会先介绍计算机网络在信息时…...
mysql CONCAT使用
问题 有一个查找数据的mysql语句:SELECT DISTINCT fund_id,version,statistic_date FROM fund_nv_divident WHERE version ( SELECT max(version) FROM fund_nv_divident) and statistic_date > ‘2023-06-04’ and fund_id not in (SELECT DISTINCT fund_id f…...
maven基础学习
什么是maven 构建 依赖 maven核心概念坐标 在黑窗口使用maven命令生成maven工程 pom.xml 想导入哪个jar包把它的坐标放到dependency里就可以 maven核心概念POM maven核心概念约定的目录结构 执行maven的构建命令 清理操作,clean 编译操作 compile 测试操作 test 打包…...
uniapp移动端地图,点击气泡弹窗并实现精准定位
记录移动端地图map组件的使用 需求记录: 移动端地图部分需要展示两个定位点,上报点及人员定位点。通过右上角的两个按钮实现地图定位。点击对应定位气泡,弹出定位点的信息。 效果图如下: map在nvue中的使用。直接用nvue可以直接…...
2023牛客暑期多校训练营7 CI「位运算」「根号分治+容斥」
C-Beautiful Sequence_2023牛客暑期多校训练营7 (nowcoder.com) 题意: 给定一个b序列,a序列满足 a [ i − 1 ] < a [ i ] a[i-1]<a[i] a[i−1]<a[i]且 a [ i ] ⊕ a [ i 1 ] b [ i ] a[i]\oplus a[i1]b[i] a[i]⊕a[i1]b[i],求字…...
YOLOv5算法改进(10)— 替换主干网络之GhostNet
前言:Hello大家好,我是小哥谈。GhostNet是一种针对计算机视觉任务的深度神经网络架构,它于2020年由中国科学院大学的研究人员提出。GhostNet的设计目标是在保持高精度的同时,减少模型的计算和存储成本。GhostNet通过引入Ghost模块…...
Android Canvas的使用
android.graphics.Canvas 一般在自定义View中,重写 onDraw(Canvas canvas) 方法时用到。 /*** Implement this to do your drawing.** param canvas the canvas on which the background will be drawn*/Overrideprotected void onDraw(Canvas canvas) {super.onDra…...
AI批量写文章伪原创:基于ChatGPT长文本模型,实现批量改写文章、批量回答问题(长期更新)
import traceback import openai import osopenai.api_key = ""conversation=[{"role": "system", "content": "You are a helpful assistant."}] max_history_len = 20 first_message = Nonedir = rJ:\ai\input #要改写的文…...
git常用场景记录 | 拉取远程分支A合并到本地分支B - 删除上一次的commit
文章目录 git常用场景记录拉取远程分支A合并到本地分支B本地分支B存在未add与commit的代码 删除上一次的commit已经push到远程库 git常用场景记录 doing,最后更新9.5 拉取远程分支A合并到本地分支B 需求描述 在团队合作时,我自己的本地分支B功能已经实现…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
数据分析六部曲?
引言 上一章我们说到了数据分析六部曲,何谓六部曲呢? 其实啊,数据分析没那么难,只要掌握了下面这六个步骤,也就是数据分析六部曲,就算你是个啥都不懂的小白,也能慢慢上手做数据分析啦。 第一…...
ffmpeg(三):处理原始数据命令
FFmpeg 可以直接处理原始音频和视频数据(Raw PCM、YUV 等),常见场景包括: 将原始 YUV 图像编码为 H.264 视频将 PCM 音频编码为 AAC 或 MP3对原始音视频数据进行封装(如封装为 MP4、TS) 处理原始 YUV 视频…...
docker容器互联
1.docker可以通过网路访问 2.docker允许映射容器内应用的服务端口到本地宿主主机 3.互联机制实现多个容器间通过容器名来快速访问 一 、端口映射实现容器访问 1.从外部访问容器应用 我们先把之前的删掉吧(如果不删的话,容器就提不起来,因…...
数据库优化实战指南:提升性能的黄金法则
在现代软件系统中,数据库性能直接影响应用的响应速度和用户体验。面对数据量激增、访问压力增大,数据库性能瓶颈经常成为项目痛点。如何科学有效地优化数据库,提升查询效率和系统稳定性,是每位开发与运维人员必备的技能。 本文结…...
