MySQL存储过程与存储函数基础详解
前言
我们大家都对MySQL函数不陌生,例如我们使用now函数可以查看当前时间
mysql> select now();
+---------------------+
| now() |
+---------------------+
| 2022-05-17 11:05:15 |
+---------------------+
1 row in set (0.00 sec)
和c,java,python一样,MySQL也可以自己创建函数,实现一些特定功能,存储过程与存储函数是一组可编程的函数,是为了完成特定功能的SQL语句集,创建并保存在数据库中,用户可通过指定存储过程与存储函数的名称并给定参数(如果需要的话)来调用执行。在基础阶段,我们完全可以把存储过程和存储函数看成相同的封装起来的封装体,因为过程就像一个没有返回值的函数,而函数不过是有返回值的过程。
创建存储过程
创建存储过程,需要使用CREATE PROCEDURE语句:
create procedure sp_name ([proc_parameter])
[characteristics...] routine_body
其中,create procedure为用来创建存储过程的关键字,sp_name为存储过程的名称,proc_parameter为指定存储过程的参数列表,参数的格式为:
[in | out | inout] param_name type
其中in表示输入参数,out表示输出参数,inout表示既可以输入也可以输出;param_name表示参数名称,type表示参数的类型,例如int等MySQL数据库中任意的数据类型
characteristics指定存储过程的特性,有以下取值:
- LANGUAGE SQL:说明routine_body部分是由SQL语句组成,当前系统支持的语言为SQL,SQL是LANGUAGE特性唯一值;
- [NOT] DETERMINISTIC:知名存储过程执行的结果是否正确,DETERMINISTIC表示结果确定的,每次执行存储过程,相同的输入会得到相同的输出。NOT DETERMINISTIC表示结果不确定,相同的输入可能得到不同的输出,如果没有指定任意一个值,默认为NOT DETERMINISTIC;
- {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}:指明子程序使用SQL语句限制。CONTAINS SQL表名子程序包含SQL,但是不包含读写数据的语句;NO SQL表示子程序不包含SQL语句;READS SQL DATA说明子程序包含读数据的语句;MODIFIES SQL DATA说明子程序包含写数据的语句,默认情况下,系统会指定为CONTAINS SQL;
- SQL SECURITY{DEFINER | INVOKER} :指明权限,DEFINER表名只有定义者才能执行,INVOKER表示拥有权限的调用者可以执行,默认为DEFINER;
- COMMENT 'string':注释信息,用于描述
目前阶段,characteristics是创建过程/函数中的可选项,这部分内容我们是用不到的
routine_body是SQL代码内容,需要用BEGIN和END来表示SQL代码的开始和结束
例1—创建存储过程
创建一个过程,该过程可以需要传入一个参数,每次调用这个过程之后,实现为这个参数数值加1的功能
mysql> #delimite可以将MySQL中默认分号(;)结束符号替换成自定义符号
mysql> #这么做是为了保证存储过程内的SQL语句能够使用分号结束
mysql> delimiter //
mysql> #创建名为t1的存储过程
mysql> #存储过程的核心功能在begin...end的范围内
mysql> create procedure t1(in x int)
-> begin
-> select x+1;
-> end //
Query OK, 0 rows affected (0.01 sec)
mysql> #调用存储过程,并且传入一个参数x=10
mysql> call t1(10) //
+------+
| x+1 |
+------+
| 11 |
+------+
1 row in set (0.00 sec)
delimiter // 语句的作用是将MySQL的结束符设置为// 因为MySQL默认结束符号是";",为了避免存储过程与SQL语句冲突,需要使用delimiter暂时改变结束符,并以END // 结束存储过程
上述代码,存储过程名为t1,并且指定了一个传入(in)的整型(int)类型的参数,参数名为x;begin表示SQL代码开始,end表示SQL代码结束,begin与end之间即为存储过程的核心代码,是一系列SQL代码的集合,可以完成一个比较复杂的功能,在上面的例子中,将传入的参数x进行数值加1操作,再输出
存储过程的调用使用call,和调用函数类似,也是过程名(参数)的形式,如果没有参数,使用过程名(),调用存储过程和函数的具体我会在下面详述
例2—创建存储过程
创建一个过程,这个过程可以输出一个参数,因此我们可以通过调用一次这个存储过程生成我们需要的参数
mysql> delimiter //
mysql> #创建存储过程
mysql> #out表明了这个过程将会有一个输出的参数
mysql> create procedure t2(out x int)
-> begin
-> set x=100;
-> end//
Query OK, 0 rows affected (0.01 sec)
mysql> #使用@p接收存储过程输出的那个参数
mysql> call t2(@p)//
Query OK, 0 rows affected (0.00 sec)
mysql> #过程调用成功之后,之后就可以任意使用@p的值
mysql> #来查看一下@p的值是多少
mysql> select @p //
+------+
| @p |
+------+
| 100 |
+------+
1 row in set (0.00 sec)
上述代码,存储过程名为t2,并且会传出(out)一个整型(int)类型的参数,参数名为x;在过程中使用:
set x=100;
这是MySQL中的赋值语句,将参数x赋值为100,之后这个参数就可以在被外部调用过程时所使用,在使用
call t2(@p)
call调用存储过程后,又将参数值赋值给@p,调用成功之后,之后随时可以使用这个变量@p
@符号表示用户变量,使用set为其赋值,用户变量与连接有关,一个客户端定义的变量不能被其它客户端看到,客户端退出时,该客户端连接的所有用户变量自动释放,用户变量可以随时随地被创建并定义
创建存储函数
存储函数和now(),sum(),max(),count()函数一样,本质上就是函数,你平时怎么使用函数,就用相同的方法去操作你自己的存储函数,创建存储函数的格式:
create function sp_name ([func_parameter])
returns type
[characteristics...] routine_body
其中,create function为用来创建存储函数的关键字,sp_name为存储函数的名称,func_parameter为指定存储过程的参数列表,参数的格式为:
param_name type
和存储过程不同 ,存储过程的参数有传入IN,传出OUT和传入传出INOUT,但是 对于存储函数来说,只有传出参数的操作 ,并且不需要也不能使用IN特别说明,param_name表示参数名称,type表示参数的类型,例如int类型等MySQL数据库中任意的类型;
returns type表示函数有一个返回值,type表明了返回数据的类型;
characteristics表示指定存储函数的特性,与存储过程相同,不再赘述,在现阶段是用不到的
routine_body也和存储过程一样,是SQL代码内容,需要用BEGIN和END来表示SQL代码的开始和结束
例1—创建存储函数
创建一个存储函数,这个函数会返回一个字符串
mysql> delimiter //
mysql> #创建一个名为t1的存储函数
mysql> create function t1()
-> returns varchar(50)
-> begin
-> return "Hello World";
-> end //
Query OK, 0 rows affected (0.00 sec)
mysql> #就像调用其它函数一样,可以直接使用select调用
mysql> select t1() //
+-------------+
| t1() |
+-------------+
| Hello World |
+-------------+
1 row in set (0.00 sec)
mysql> #也可以像调用其它函数一样作为一个参数传入其它函数
mysql> select concat("<",t1(),">") //
+----------------------+
| concat("<",t1(),">") |
+----------------------+
| <Hello World> |
+----------------------+
1 row in set (0.00 sec)
注意在创建函数时,如果你的MySQL报错:
ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA
in its declaration and binary logging is enabled (you *might* want to use the less safe
log_bin_trust_function_creators variable)
那么有两种解决方法,第一种,在存储函数的特性(characteristics)部分加上READS SQL DATA说明子程序包含读数据的语句,所以上面的示例应该改成:
mysql> create function t1()
-> returns varchar(50)
-> READS SQL DATA
-> begin
-> return "Hello World";
-> end //
Query OK, 0 rows affected (0.01 sec)
第二种,在配置文件中添加
# 在创建存储函数时,可以不使用characteristics
log_bin_trust_function_creators=1
重启MySQL服务器之后即可,不需要添加特性的内容也可以创建成功了
到这里小结一下,存储过程和存储函数的主要区别:
- 参数:存储过程对待参数有三种方式:输入(IN),输出(OUT)和输入并输出(INOUT),因为有三种方式所以必须对参数指明其用途;对于存储函数只有一种方式:输入参数,因此不需要写IN;
- 存储函数有一个returns type(注意这里的return后加上s),存储过程没有也不需要这个返回语句。另外需要说明的是,returns只能对function做指定,对函数而言这是 强制 的,它用来指定函数的返回类型,因此函数体内必须包含一个return值,即一句return的代码
- 调用存储过程使用call,调用存储函数可以直接使用select查看函数返回值,也可以作为其它函数的参数
调用存储过程和函数
存储过程和函数有多种调用方法,存储过程必须使用 call语句 调用,而且存储过程和数据库相关,如果要执行其它数据库中的存储过程,需要指定数据库名,例如CALL dbname.sp_name。存储函数的调用与预定义的函数调用方法相同
调用存储过程
mysql> call sp_name([parameter[,...])
在存储过程中的参数parameter部分(如果有的话),如果是输入(IN)的参数,需要直接按照对应的数据类型放入对应的位置,如果是输出(OUT)的参数,可以使用@var_name用户变量接收
调用存储函数
在MySQL中,存储函数的使用方法和MySQL内部的函数使用方法是一样的,虽然存储函数和存储过程的定义有所不同,但是可以实现相同的功能,在实际应用中需要灵活选择
查看存储过程和函数
SHOW STATUS
使用show status可以查看存储过程和存储函数
show {procedure | function} status [like 'pattern'] \G
这个是MySQL的扩展,返回子程序的特征,例如数据库,名字,类型创建者及创建和修改日期,如果没有指定procedure或function,那么所有过程和函数会被列出,LIKE语句表示匹配存储过程或者存储函数的名字,例如
mysql> show procedure status like "t1"\G
*************************** 1. row ***************************
Db: test
Name: t1
Type: PROCEDURE
Definer: root@localhost
Modified: 2022-05-17 11:28:48
Created: 2022-05-17 11:28:48
Security_type: DEFINER
Comment:
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
Database Collation: utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
SHOW CREATE
show create {procedure | function} sp_name\G
使用show create可以查看存储过程和存储函数,例如:
mysql> show create procedure t1\G
*************************** 1. row ***************************
Procedure: t1
sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `t1`(in x int)
begin
select x+1;
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
Database Collation: utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
information_schema.Routines
MySQL中存储过程和函数的信息存储在information_schema数据库下Routines表中,在information_schema数据库下Routines表中存储了所有过程和函数的定义,使用select查询时,一定要使用Routine_name字段指定过程或函数的名字,否则会查出所有过程和函数,如果有过程和函数同名,则需要同时指定Routine_type字段(PROCEDURE | FUNCTION,注意大写)区分是过程还是函数,例如查询某个名为t1的过程或函数
mysql> 进入information_schema数据库中
mysql> use information_schema;
Database changed
mysql> #查询t1的存储过程或者存储函数
mysql> #如果有一个存储过程和一个存储函数同名,那么都会被查询到
mysql> select * from Routines where Routine_name = "t1"\G
*************************** 1. row ***************************
SPECIFIC_NAME: t1
ROUTINE_CATALOG: def
ROUTINE_SCHEMA: test
ROUTINE_NAME: t1
ROUTINE_TYPE: FUNCTION
DATA_TYPE: varchar
CHARACTER_MAXIMUM_LENGTH: 50
CHARACTER_OCTET_LENGTH: 200
NUMERIC_PRECISION: NULL
NUMERIC_SCALE: NULL
DATETIME_PRECISION: NULL
CHARACTER_SET_NAME: utf8mb4
COLLATION_NAME: utf8mb4_0900_ai_ci
DTD_IDENTIFIER: varchar(50)
ROUTINE_BODY: SQL
ROUTINE_DEFINITION: begin
return "Hello World";
EXTERNAL_NAME: NULL
EXTERNAL_LANGUAGE: SQL
PARAMETER_STYLE: SQL
IS_DETERMINISTIC: NO
SQL_DATA_ACCESS: READS SQL DATA
SQL_PATH: NULL
SECURITY_TYPE: DEFINER
CREATED: 2022-05-18 01:53:49
LAST_ALTERED: 2022-05-18 01:53:49
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
ROUTINE_COMMENT:
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
*************************** 2. row ***************************
SPECIFIC_NAME: t1
ROUTINE_CATALOG: def
ROUTINE_SCHEMA: test
ROUTINE_NAME: t1
ROUTINE_TYPE: PROCEDURE
DATA_TYPE:
CHARACTER_MAXIMUM_LENGTH: NULL
CHARACTER_OCTET_LENGTH: NULL
NUMERIC_PRECISION: NULL
NUMERIC_SCALE: NULL
DATETIME_PRECISION: NULL
CHARACTER_SET_NAME: NULL
COLLATION_NAME: NULL
DTD_IDENTIFIER: NULL
ROUTINE_BODY: SQL
ROUTINE_DEFINITION: begin
select x+1;
EXTERNAL_NAME: NULL
EXTERNAL_LANGUAGE: SQL
PARAMETER_STYLE: SQL
IS_DETERMINISTIC: NO
SQL_DATA_ACCESS: CONTAINS SQL
SQL_PATH: NULL
SECURITY_TYPE: DEFINER
CREATED: 2022-05-17 11:28:48
LAST_ALTERED: 2022-05-17 11:28:48
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
ROUTINE_COMMENT:
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
2 rows in set (0.01 sec)
mysql> #我们增加一个查询条件,只查询名为t1的存储过程
mysql> #注意Routine_type="PROCEDURE"的PROCEDURE必须全部大写,因为这是一个数据
mysql> select * from Routines where Routine_name = "t1" and Routine_type="PROCEDURE"\G
*************************** 1. row ***************************
SPECIFIC_NAME: t1
ROUTINE_CATALOG: def
ROUTINE_SCHEMA: test
ROUTINE_NAME: t1
ROUTINE_TYPE: PROCEDURE
DATA_TYPE:
CHARACTER_MAXIMUM_LENGTH: NULL
CHARACTER_OCTET_LENGTH: NULL
NUMERIC_PRECISION: NULL
NUMERIC_SCALE: NULL
DATETIME_PRECISION: NULL
CHARACTER_SET_NAME: NULL
COLLATION_NAME: NULL
DTD_IDENTIFIER: NULL
ROUTINE_BODY: SQL
ROUTINE_DEFINITION: begin
select x+1;
EXTERNAL_NAME: NULL
EXTERNAL_LANGUAGE: SQL
PARAMETER_STYLE: SQL
IS_DETERMINISTIC: NO
SQL_DATA_ACCESS: CONTAINS SQL
SQL_PATH: NULL
SECURITY_TYPE: DEFINER
CREATED: 2022-05-17 11:28:48
LAST_ALTERED: 2022-05-17 11:28:48
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
ROUTINE_COMMENT:
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
修改过程和函数
目前MySQL还不提供已存在的存储过程代码的修改,如果必须要修改,需要使用DROP删除后重新创建
删除过程或函数
drop {procedure | function} sp_name;
变量的使用
在存储过程和函数中 ,可以定义和使用变量,用户可以使用关键字DECLARE来定义变量,然后为变量赋值,这些变量主要范围是begin...end的程序体中
定义变量
declare var_name [,var_name]... type [default value];
var_name为局部变量名称,type是MySQL中的数据类型,default value给变量提供一个默认值,除了可以用一个常数之外,还可以使用表达式,如果没有default,则为null,例如:
declare myparam int default 100;
上面代码定义了一个int类型的变量,变量名为myparam,默认值为100
变量赋值
set var_name = expr [,var_name = expr] ...;
在存储程序中的set语句是一般set语句的扩展版本,被参考变量可能是子程序内声明的变量,或者是全局服务器变量,如系统变量或者用户变量,这句话的意思是,使用set可以为declare定义的变量赋值,也可以为用户变量(即带@符号的变量)赋值
例如,我们定义三个变量var1,var2和var3
declare var1,var2,var3 int;
set var1=10,var2=20;
set var3=var1+var2;
在MySQL中,还可以使用SELECT...INTO...将数据表中数据或某些数据赋值给变量,例如,我们将test表中的id为1的数据中的v1和v2字段的值分别赋值给var_1和var_2
select v1,v2 into var_1 ,var_2 from test where id = 1;
定义条件和处理程序(异常)
特定条件需要特定处理,定义条件是事先定义程序执行过程中遇到的问题,而处理程序定义了遇到这些问题时应当采取的处理方式,并且 保证存储过程或函数在遇到警告或错误时能继续执行 ,避免程序异常停止,使用DECLARE关键字来定义条件和处理程序
类似Java中的try...catch或者python中的try...except...,虽然MySQL中的条件和处理程序和它们完全不同,但是都可以看作对待异常的处理方式
定义条件
declare condition_name condition for [condition_type]
condition_name参数表示条件的名称,condition_type表示条件的类型,有以下两种类型:
- sqlstate_value:表示MySQL的错误,sqlstate_value为长度5字符串类型的错误代码
- mysql_error_code:表示MySQL的错误,为数值类型的错误代码
我们以主键冲突的错误代码为例,5字符串类型的错误代码"23000"和数值类型的错误代码1062在MySQL中都表示的是主键冲突
ERROR 1062 (23000): Duplicate entry '1' for key 'test.PRIMARY'
例1—定义条件
定义一个条件,自定义条件名为PRIMARY_Duplicate,表示主键冲突,使用sqlstate_value定义
declare PRIMARY_Duplicate condition for sqlstate '23000';
例2—定义条件
定义一个条件,自定义条件名为PRIMARY_Duplicate,表示主键冲突,使用mysql_error_code定义
declare PRIMARY_Duplicate condition for 1062;
例1与例2的结果完全相同
定义处理程序
使用declare...handler语句指定需要特殊处理的条件,即定义一个处理方式,当遇到一个条件时
declare handler_type handler for condition_value sp_statement;
handler_type为错误处理方式有三种:
- continue:表示遇到错误不处理;
- exit:表示遇到错误马上退出;
- undo:表示遇到错误撤回之前操作
condition_value表示错误类型:
- sqlstate_value:表示MySQL的错误,sqlstate_value为长度5字符串类型的错误代码;
- mysql_error_code:表示MySQL的错误,为数值类型的错误代码;
- condition_name:表示自定义的条件名称;
- sqlwarning:匹配任何01开头的sqlstate错误代码;
- not found:匹配任何02开头的sqlstate错误代码;
- sqlexception:匹配所有没有被sqlwarning或not found捕获的sqlstate错误代码
sp_statement 为程序语句段,表示在遇到定义的错误时需要执行的操作
通常情况下,执行过程遇到错误应该立刻停止执行下面的语句,并且撤回之前的操作,但是MySQL目前不支持 UNDO操作,因此最好执行 EXIT 操作,如果事先能够预测错误,并且进行相应的处理,那么可以执行 CONTINUE 操作
下面我将使用六个例子说明处理程序的定义方式,在平时只要会使用其中一种即可
例1—定义处理程序
定义一个处理程序,当捕获sqlstate_value为"42s02"时,执行continue继续操作,并且输出信息
declare continue handler for sqlstate '42s02' set @info='NO_SUCH_TABLE';
例2—定义处理程序
定义一个处理程序,当捕获mysql_error_code为1146时,执行continue继续操作,并且输出信息
declare continue handler for 1146 set @info="NO_SUCH_TABLE";
例3—定义处理程序
首先定义一个条件no_SUCH_TABLE,该条件表示遇到mysql_error_code为1146的问题,再定义一个处理程序,当捕获条件为no_SUCH_TABLE时,执行continue继续操作,并且输出信息
declare no_SUCH_TABLE condition for 1146;
declare continue handler for no_SUCH_TABLE set @info="NO_SUCH_TABLE";
例4—定义处理程序
定义一个处理程序,当遇到sqlwarning问题时,执行exit马上退出程序,并且输出信息
declare exit handler for sqlwarning set @info="ERROR";
例5—定义处理程序
定义一个处理程序,当遇到not found问题时,执行exit马上退出程序,并且输出信息
declare exit handler for not found set @info="ERROR";
例6—定义处理程序
定义一个处理程序,当遇到sqlexception问题时,执行exit马上退出程序,并且输出信息
declare exit handler for sqlexception set @info="ERROR";
一个实例
最后,我们把处理程序放入存储过程中进行展示,学习和了解程序在过程中发挥的作用
mysql> delimiter //
mysql> #创建一个表test,只有一个主键字段
mysql> create table test(id int primary key)//
Query OK, 0 rows affected (0.02 sec)
mysql> #创建一个过程,在过程中定义一个程序,程序在遇到主键冲突时仍然继续执行
mysql> #定义程序之后,尝试往test表插入两个相同的主键数据
mysql> #使用@info标记过程内部代码块执行的位置
mysql> create procedure Handler_for_ERROR()
-> begin
-> declare continue handler for sqlstate '23000' set @info="Step-1";
-> insert into test values(1);
-> set @info="Step-2";
-> insert into test values(1);
-> set @info="Step-3";
-> end//
Query OK, 0 rows affected (0.00 sec)
mysql> #调用过程,可依发现调用成功
mysql> call Handler_for_ERROR()//
Query OK, 0 rows affected (0.01 sec)
mysql> #查看@info变量的值,说明即使发生主键冲突,但是对于过程来说,还是会继续执行
mysql> select @info//
+--------+
| @info |
+--------+
| Step-3 |
+--------+
1 row in set (0.00 sec)
同学们可以自行进行尝试对比,如果存储过程内没有定义的处理程序,那么发生主键冲突时,会立即报错并退出过程,导致调用过程时失败
光标
查询语句可能返回多条行记录,如果数据量非常大,需要在 存储过程和存储函数 中使用光标来 逐条读取 查询结果集的记录
定义光标
declare cursor_name cursor for select_statement;
其中,cursor_name表示光标名称,select_statement表示普通的select查询语句,例如,我们定义一个名为cur的光标, 光标用于逐行查询test表的行数据
declare cur cursor for select * from test;
打开光标
open cursor_name;
cursor_name表示之前已经定义的光标名称
使用光标
fetch cursor_name into val1 [,val2 ...];
将光标中的信息存入变量val中,这个val值不能使用用户变量(即@开头的变量),变量必须在之前已经被定义。成功赋值之后,光标会自动移动下一行(可以通过一个循环结构实现光标的移动,关于循环结构会在下文流程控制中详细介绍),如果没有下一条数据再使用fetch为变量赋值时会报错
关闭光标
close cursor_name;
cursor_name表示之前已经定义的光标名称,如果没有关闭光标,默认在存储过程/存储函数的END后自动关闭
存储过程/存储函数中各种数据定义的顺序
这里有一个非常重要的点,现在我们知道在一个存储过程/存储函数中,可以定义变量,定义条件和程序,定义光标,但是这些数据在被定义时具有较为严格的出场顺序,假设现在有一个存储过程/存储函数同时需要用到变量、条件和程序、光标,那么,必须按照下列顺序执行:定义变量->定义光标但是不开启光标->定义条件和程序->开启光标,示例:
create procedure test()
begin
declare a int default 0;
declare cur cursor for select ...
declare b condition for ...
declare exit handler for ...
open cur;
...
流程控制的使用
路程控制语句用来根据条件控制语句的执行,MySQL中有以下七种语句:
- IF语句
- CASE语句
- LOOP语句
- ITERATE语句
- LEAVE语句
- REPEAT语句
- WHILE语句
IF语句
if语句包含多个条件判断,根据判断结果为True或False执行相应的语句
if expr_condition then statement_list
[elseif expr_condition then statement_list] ...
[else statement_list]
end if
IF实现了一个基本条件构造,如果expr_condition求值为真,相应的SQL语句列表被执行,否则判断elseif的条件或执行else的SQL语句
例1—IF语句
这是一个简单的条件判断语句,如果2>1是True输出"2>1",如果1<2是True输出"2<1",如果if和elseif的条件都为False则输出NULL
mysql> create procedure test()
-> begin
-> if 2>1 then select "2>1";
-> elseif 1<2 then select "1<2";
-> else select "NULL";
-> end if;
-> end//
Query OK, 0 rows affected (0.01 sec)
mysql> call test()//
+-----+
| 2>1 |
+-----+
| 2>1 |
+-----+
1 row in set (0.00 sec)
IF条件判断语句
额外介绍一下IF判断函数,这是MySQL内置函数,它的格式为
IF(expr,v1,v2)
如果表达式expr是TRUE则返回v1,否则返回v2,例如:
mysql> select if(2>1,"True","False")//
+------------------------+
| if(2>1,"True","False") |
+------------------------+
| True |
+------------------------+
1 row in set (0.00 sec)
还有一个IFNULL判断是否为空函数
IFNULL(v1,v2)
如果v1不为NULL返回v1,否则返回v2,例如:
mysql> select ifnull(null,2),ifnull(1,null)//
+----------------+----------------+
| ifnull(null,2) | ifnull(1,null) |
+----------------+----------------+
| 2 | 1 |
+----------------+----------------+
1 row in set (0.00 sec)
CASE语句
CASE是另一个进行条件判断的语句,有两种格式
CASE的第一种格式
case case_expr
when when_value then statement_list
[when when_value then statement_list] ...
[else statement_list]
end case
case_expr表示条件判断的表达式,决定了哪一个WHEN被执行,when_value表示表达式可能的值,如果某一个when_value与表达式值相同,则执行相应的THEN关键字后面的statement_list,如果所有when_value均不匹配,执行else之后的SQL语句
例1—CASE语句
这是一个简单的条件判断语句,给出条件判断的表达式1,只执行某一个when_value=1的SQL语句,如果WHEN之后的when_value都没有与之条件匹配的1,那么执行ELSE之后的语句
mysql> create procedure test()
-> begin
-> case 1
-> when 1 then select "1";
-> when 2 then select "2";
-> else select "Other";
-> end case;
-> end//
Query OK, 0 rows affected (0.00 sec)
mysql> call test()//
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
CASE的第二种格式
case
when expr_condition then statement_list
[when expr_condition then statement_list] ...
[else statement_list]
end case
相较于第一种格式,这种方式没有了case_expr条件判断的表达式,哪一个WHEN被执行,是由WHEN之后的expr_condition决定的,如果expr_condition表达式为True,则执行对应的SQL,如果为False则不执行,如果所有WHEN都不执行,则执行else之后的SQL语句
例2—CASE语句
mysql> create procedure test()
-> begin
-> case
-> when 2>1 then select "2>1";
-> when 2<1 then select "2<1";
-> else select NULL;
-> end case;
-> end//
Query OK, 0 rows affected (0.01 sec)
mysql> call test()//
+-----+
| 2>1 |
+-----+
| 2>1 |
+-----+
1 row in set (0.00 sec)
CASE条件判断语句
在MySQL内置函数中,也有CASE的函数,并且也有两种格式,第一种格式:
CASE expr WHEN v1 THEN r1 WHEN v2 THEN r2 ... ELSE r END
如果expr的值等于某个WHEN后面的值,则返回对应的THEN,如果每个WHEN都不匹配,则返回ELSE后面的值
第二种格式:
CASE WHEN v1 THEN r1 WHEN v2 THEN r2.... ELSE r END
当某个vn的值为TRUE时返回THEN后面的rn,如果均不为TRUE,则返回ELSE后面的值
LOOP语句
LOOP循环用来重复执行某些语句,LOOP创建了一个循环操作的过程,不会进行条件的判断,LOOP内的语句一直重复执行直到退出(使用LEAVE子句),跳出循环过程,LOOP的基本格式:
loop_label:loop
statement_list
end loop loop_name
其中loop_name表示循环的名称,定义循环的名称之后同时相当于定义了一个label标签,statement_list表示需要循环的sql语句
例1—LOOP语句
由于LOOP没有离开循环的条件,所以需要使用LEAVE语句跳出循环
mysql> #创建一个循环,用户输入一个整型变量x,计算从1加到x的和
mysql> create procedure test(in x int)
-> begin
-> declare i int default 1;
-> set @sum=0;
-> t_loop:loop
-> if i<=x then set @sum=@sum+i;
-> set i=i+1;
-> else leave t_loop;
-> end if;
-> end loop t_loop;
-> end//
Query OK, 0 rows affected (0.01 sec)
mysql> call test(100)//
Query OK, 0 rows affected (0.00 sec)
mysql> select @sum//
+------+
| @sum |
+------+
| 5050 |
+------+
1 row in set (0.00 sec)
LEAVE语句
LEAVE语句用来退出任何被标签的流程控制构造,格式:
leave label_name
label_name是流程控制语句的名称(即标签),LEAVE语句可以出现在LOOP、REPEAT、WHILE语句内,因为循环结构可以嵌套,因此每一个流程控制结构都需要一个标签,这样就可以使用LEAVE关键字指定退出哪一个循环体
ITERATE语句
ITERATE语句将执行顺序转到流程控制内开头处,格式:
iterate label_name
label_name是流程控制语句的名称(即标签),ITERATE的意思是再次循环,可以出现在LOOP、REPEAT、WHILE语句内,ITERATE语句必须跟在循环标签前面
REPEAT语句
REPEAT语句创建一个带条件的循环结构,每次循环执行完之后会对表达式进行判断,如果判断为真,则循环结束,否则重复执行循环的语句
repeat_label:repeat
statement_list
until expr_condition
end repeat repeat_label
其中repeat_label表示循环的名称(即标签),statement_list表示需要循环的sql语句,expr_condition表示执行一次循环之后进行判断,如果判断为真,则循环结束,否则重复执行循环的语句
例1—REPEAT语句
mysql> #创建一个循环,用户输入一个整型变量x,计算从1加到x的和
mysql> create procedure test(in x int)
-> begin
-> declare i int default 0;
-> set @sum=0;
-> t_repeat:repeat
-> set @sum=@sum+i;
-> set i=i+1;
-> until i>x
-> end repeat t_repeat;
-> end//
Query OK, 0 rows affected (0.00 sec)
mysql> call test(100)//
Query OK, 0 rows affected (0.00 sec)
mysql> select @sum//
+------+
| @sum |
+------+
| 5050 |
+------+
1 row in set (0.00 sec)
注意until的语句后面不需要分号(;)
WHILE语句
WHILE语句创建一个带条件判断的循环过程,与REPEAT不同,WHILE是在执行语句执行之前判断,条件为真则执行循环的语句,否则退出循环
while_label:while expr_condition DO
statement_list
end while while_label;
其中while_label表示循环的名称(即标签),statement_list表示需要循环的sql语句,expr_condition表示执行一次循环之前进行的判断,如果判断为真,则进入循环,否则跳出循环
例1—WHILE语句
mysql> #创建一个循环,用户输入一个整型变量x,计算从1加到x的和
mysql> create procedure test(in x int)
-> begin
-> declare i int default 0;
-> set @sum=0;
-> t_while:while i<=x do
-> set @sum=@sum+i;
-> set i=i+1;
-> end while t_while;
-> end//
Query OK, 0 rows affected (0.01 sec)
mysql> call test(100)//
Query OK, 0 rows affected (0.00 sec)
mysql> select @sum//