MySQL常用操作之创建存储过程语法详解
前言
-
场景介绍
作为一名Java搬运工,实际开发中经常使用定时任务来进行业务批处理,实际上通过数据库存过定时执行也能达到同样的效果。且存在两大好处:
-
通过修改存储过程的方式修改业务逻辑,不需要重启服务器。
-
存储过程将每一条SQL语句都进行编译,并保存在数据库当中,那么我们通过Java程序调用SQL语句时可以不用一条条的进行编译,而是直接调用存储过程,效率更高。
-
简介
-
概念
存储过程(Stored Procedure)是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对象,它可以封装成SQL语句集,以便完成一些较为复杂的业务逻辑,并且可以想 Java 等高级编程语言一样输入参数。
存储过程是为了完成特定功能的SQL语句集,创建时会预先编译,并保存在数据库中,用户后续的调用都不需要再次编译了,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。
语法
创建结构
-
创建存过模板
CREATE [DEFINER = { user | CURRENT_USER }] PROCEDURE sp_name ([proc_parameter[,...]]) [characteristic ...] routine_body
参数介绍
- proc_parameter参数部分(先写输入/输出参数名,再写参数类型)
[ IN | OUT | INOUT ] param_name type
-
type 是 MySQL 支持的所有类型,如int、varchar等
-
routine_body(程序体)部分 ,可以书写合法的SQL语句
BEGIN ...... END$$
-
声明语句结束符
-- $$ 可以自定义为其他作为结束符 DELIMITER $$
-
声明存储过程
CREATE PROCEDURE nbsp_procedure(IN 参数名 参数类型)
-
存储过程开始和结束符
BEGIN .... END
-
创建示例
-- 声明语句结束符,可以自定义: delimiter $$ -- 创建存储过程,名为 nbsp_procedure 参数为空, 执行输出 'hello world' create procedure nbsp_procedure () -- 默认当前用户下创建 create definer = `nbsp`@`%` procedure nbsp_procedure () begin select 'hello world'; end $$ -- 声明语句结束符 delimiter ; -- 调用存储过程 call nbsp_procedure(); -- 删除存储过程 drop procedure nbsp_procedure;
-
注意事项
-
这里需要注意的是 DELIMITER$$ 和 DELIMITER; 两句,DELIMITER是声明分割符的意思,因为MySQL默认以 “;” 为分隔符,如果我们没有声明分割符,那么编译器会把存储过程当成SQL语句进行处理,则存储过程的编译过程会报错,所以要事先用DELIMITER关键字申明当前段分隔符,这样MySQL才会将“;”当做存储过程中的代码,不会执行这些代码,用完了之后要把分隔符还原。
-
存储过程根据需要可能会有输入、输出、输入输出参数,如果有多个参数用“,”分割开。
-
过程体的开始与结束使用BEGIN与END进行标识
-
变量结构
-
变量分类
MySQL变量一共分为两大类:用户自定义变量和系统变量。
-
用户自定义变量(重点):局部变量、会话变量
-
系统变量(理解):会话变量、全局变量
-
-
局部变量
用户自定义在begin…end代码块中的,也在代码块中有效。
-- 语法 declare 变量名 变量类型 [default 默认值] -- 举例 declare name varchar(32) '张三' -- set 赋值 delimiter $$ drop procedure if exists var01$$ create procedure var01() begin -- 声明变量 declare username varchar(32) default '张三'; -- 为变量赋值 set username = '李四'; select username; end $$ delimiter ; call var01();
-
会话变量
会话变量即为服务器为每个客户端连接维护的变量。在客户端连接时,使用相应全局变量的当前值对客户端的回话变量进行初始化。
设置会话变量不需要特殊权限,但客户端只能更改自己的会话变量。其作用域与生命周期均限于当前客户端连接。
@username 会话变量可以在 begin … end 代码块中定义,也可以在外部定义,定义后同一变量名在内存中只有一份,且当前会话有效。
delimiter $$ drop procedure if exists var02$$ -- 创建存储过程 create procedure var02() begin set @username='张三'; end $$ delimiter ; -- 调用存储过程 call var02(); -- 查询会话变量的值 select @username;
入参变量和出参变量
-
IN 输入变量
表示该参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,为默认值
仅需要将数据传入存储过程,并不需要返回计算后的该值。
只能当做传入参数。
-
OUT 输出参数
该值可在存储过程内部被改变,并可返回
不接受外部传入的数据,仅返回计算之后的值。
只能当做转出参数。
-
INOUT输入输出参数
调用时指定,并且可被改变和返回
需要数据传入存储过程经过调用计算后,再传出返回值
可当做传入转出参数。
-
演示示例
- 创建一个会话变量,用于测试out对会话变量的影响
set @username='wangwu';
- 创建一个var03存过,返回用户名
delimiter $$ drop procedure if exists var03$$ -- 传入两个变量,第一个为输入in变量,第二个为是输出out变量。当输入用户id 时,返回用户名。 create procedure var03(in id int,out username varchar(32)) begin select id,username; -- select 'procedure into username' into username; -- set username='procedure into username' end $$ delimiter ;
- 查询会话变量用户名
-- 在还未调用存储过程时,我们发现此时的会话变量@username的值依然为 wangwu select @username;
- 调用存过,查看会话变量值是否正确
call var03(35,@username);
原因分析:这里就是 in 和 out 参数的区别, 也有区别与Java等高级语言中的参数。
如果是以Java的思想理解调用存储过程的话,我们可能会认为说 @username变量的值为wangwu 传入到 存储过程函数中,所以我们也理所应当的认为 username的值应该为wangwu。
out函数它只负责变量的接收,不负责传入值。 也就是说 调用call var03(35,@username); 的时候,@username只是将它的内存地址传进去,告诉存储过程说,等等你把out变量username的值给我(我是@username)。然后在存储过程中一直都没有给username进行赋值的操作,所以此时的username的值为null,最终就将null赋值给了 @username。
- 查询会话变量用户名
-- 此时会话变量为NULL
select @username;
流程控制
-
内容介绍
在存储过程和自定义函数中可以使用流程控制语句来控制程序的流程。
MySQL 中流程控制语句有:IF 语句、CASE 语句、LOOP 语句、LEAVE 语句、ITERATE 语句、REPEAT 语句和 WHILE 语句等。
判断(IF 语句)
-
定义
IF 语句用来进行条件判断,根据是否满足条件(可包含多个条件),来执行不同的语句,是流程控制中最常用的判断语句。
-
语法
IF search_condition THEN statement_list [ELSEIF search_condition THEN statement_list]... [ELSE statement_list] END IF ------------------------------------------------------------------------------------------ IF 判断条件 THEN 执行语句 [ELSELF 判断条件 THEN 执行语句] [ELSE 执行语句] END IF
-
参数介绍
search_condition 参数表示条件判断语句,如果返回值为 TRUE ,相应的 SQL 语句列表(statement_list)被执行;如果返回值为 FALSE,则 ELSE 子句的语句列表被执行。statement_list 可以包括一个或多个语句。
-
示例
set @username = '张三风'; -- 存过创建失败,不清楚原因 delimiter $$ drop procedure if exists if01$$ create procedure if01() begin IF @username = '张三' THEN select concat('我是',@username); ELSEIF @username = '张三风' THEN select '张三是我徒弟'; ELSE select '我是栗四,不认识张三'; END IF; end $$ delimiter ; call if01;
创建失败原因:最后一个end 后面要跟 “;” 号,可以创建成功,但是执行完依旧报错1064 - ‘$$’ at line 10;但是创建其他存过一样的写法创建正常。
-
注意事项
MySQL 中的 IF( ) 函数不同于这里的 IF 语句。
判断(CASE 语句)
-
定义
CASE 语句也是用来进行条件判断的,它提供了多个条件进行选择,可以实现比 IF 语句更复杂的条件判断。
-
语法
语法一:expr_condition为条件表达式,计算结果为true,则执行THEN后面的语句。多个WHEN…THEN依次执行,ELSE为可选条件。
CASE WHEN expr_condition THEN ...do something... WHEN expr_condition THEN ...do something... ....多个 WHEN THEN 语句..... [ELSE ...do something...] END CASE;
语法二:其中,case_value表示条件判断的表达式,WHEN后的表达式结果如果和case_value匹配,则执行相应的THEN后面的语句。没有则执行ELSE,ELSE为可选。(建议使用第一种,逻辑清晰一点)。
CASE case_value WHEN when_value THEN statement_list [WHEN when_value THEN statement_list]... [ELSE statement_list] END CASE ------------------------------------------------------------------ CASE 参数变量 WHEN 参数变量1 THEN 执行语句] [WHEN 参数变量2 THEN 执行语句] [ELSE 执行语句] END CASE
-
参数介绍
-
case_value 参数表示条件判断的变量,决定了哪一个 WHEN 子句会被执行;
-
when_value 参数表示变量的取值,如果某个 when_value 表达式与 case_value 变量的值相同,则执行对应的 THEN 关键字后的 statement_list 中的语句;
-
statement_list 参数表示 when_value 值没有与 case_value 相同值时的执行语句。
-
CASE 语句都要使用 END CASE 结束。
-
-
示例
DELIMITER $$ CREATE PROCEDURE proc1(IN parameter int) begin declare var int; set var=parameter+1; case var when 0 then insert into t values(17); when 1 then insert into t values(18); else insert into t values(19); end case; end; $$ DELIMITER ;
-
注意事项
方式二类似与 Java 中的 switch…case 语句的使用。 与之区别就是执行时,只要进入其中的一个判断语句之后,就不会在执行其他的,也不需要使用break语句进行跳出。
循环(LOOP 语句)
-
定义
反复执行循环体中的语句,直到循环结束。如果没有leave等关键字,此时的loop为死循环。
-
语法
[begin_label:] LOOP statement_list ; END LOOP [begin_label]; ------------------------------------------------------------------ 循环名: loop -- 循环体 执行语句; end loop 循环名;
-
示例
-- 需求: 打印1-10,输出结果为 1,2,3,4......9,10 -- 如果存在loop_test,则先删除 drop procedure if exists loop_test; delimiter $$ -- 创建存储过程loop_test create procedure loop_test() begin declare c_index int default 1; declare result_str varchar(256) default '1'; cnt: loop -- 循环10次,则跳出循环 if c_index >= 10 then leave cnt; end if; -- 自增 set c_index = c_index + 1; -- 拼接 select concat(result_str, ',', c_index) into result_str; end loop cnt; -- 查询结果 select result_str; end $$ delimiter ; call loop_test();
-
注意事项
loop循环不需要初始条件,这点和while 循环相似,同时和repeat循环一样不需要结束条件。
LEAVE关键字作用
leave 就相当于Java中的break语句,用于跳出当前循环
Java类比代码
public void loop_test() { String result_str = "1"; int c_index = 1; while (true) { if (c_index >= 10) { break; } c_index = c_index + 1; result_str = result_str + "," + c_index; } System.out.println(result_str); }
循环(WHILE 语句)
-
定义
-
语法
[WHILE标注] : WHILE 条件表达式 DO 执行语句 END LOOP [WHILE标注];
-
参数介绍
“WHILE标注” 为可选,表示WHILE循环语句的标签。
-
示例
drop procedure if exists sp_flow_while; delimiter $$ -- 创建存储过程sp_flow_while create procedure sp_flow_while() begin declare c_index int default 1; -- 收集结果集字符串 declare result_str varchar(256) default '1'; while c_index < 10 do set c_index = c_index + 1; set result_str = concat(result_str, ',', c_index); end while; -- 查询结果 select result_str; end $$ delimiter ; call sp_flow_while();
-
注意事项
WHILE的执行过程是:先判断条件表达式 expr_condition,如果为true执行循环,否则结束循环(与REPEAT的区别是:WHILE先判断条件,REPEAT是后判断条件)。
循环(REPEAT 语句)
-
定义
repeat在执行操作后检查结果,而while则是执行前进行检查。
-
语法
[REPEAT标注]:REPEAT 执行语句 UNTIL 条件表达式 END REPEAT [REPEAT标注]
-
参数介绍
-
“REPEAT标注” 为可选,表示REPEAT循环语句的标签;
-
UNTIL指定循环条件。
-
-
示例
drop procedure if exists test; -- 如果存在test存储过程则删除 delimiter $$ -- 定义标识符为双斜杠 create procedure test() -- 创建无参存储过程,名称为test begin declare i int default 6; -- 申明变量 repeat insert into test values (i); -- 往test表添加数据 set i = i + 1; -- 循环一次,i加一 until i > 10 end repeat; -- 结束循环的条件: 当i大于10时跳出repeat循环 select * from test; -- 查看test表数据 end; $$ -- 结束定义语句 delimiter ;
-
注意事项
REPEAT执行过程是:每次循环体执行完毕需要去判断一下条件表达式expr_condition,如果为true继续执行,否则结束循环。
再次循环(ITERATE 语句)
-
定义
再次循环,将执行顺序转到语句开头处,只能用于LOOP,REPEAT和WHILE语句内。
-
语法
ITERATE [LOOP/REPEAT/WHILE标注]
-
参数介绍
类似于Java代码语言的continue
-
示例
drop procedure if exists test; -- 如果存在test存储过程则删除 delimiter $$ -- 定义标识符为双斜杠 create procedure test() -- 创建无参存储过程,名称为test begin declare i int(11) default 0; -- 申明变量 myloop:loop set i = i + 1; if i < 5 then iterate myloop; -- 跳过本次循环 elseif i > 8 then leave myloop; end if; insert into test values (i); end loop myloop; select * from test; end; $$ delimiter ; -- 结束定义语句
总结
-
优点
-
存储过程可封装,并隐藏复杂的商业逻辑。
-
存储过程可以回传值,并可以接受参数。
-
存储过程无法使用 SELECT 指令来运行,因为它是子程序,与查看表,数据表或用户定义函数不同。
-
存储过程可以用在数据检验,强制实行商业逻辑等。
-
-
缺点
-
存储过程,往往定制化于特定的数据库上,因为支持的编程语言不同。当切换到其他厂商的数据库系统时,需要重写原有的存储过程。
-
存储过程的性能调校与撰写,受限于各种数据库系统。
-
参考链接
-
MySQL(七)存储过程
https://blog.csdn.net/Dim_Jerry/article/details/110203466
-
mysql存储过程、存储函数(二):流程控制语句
https://blog.csdn.net/weixin_40482816/article/details/112258414
-
mysql创建定时执行存储过程任务
https://www.cnblogs.com/master-zxc/p/6429266.html
-
mysql定时任务(event事件)
https://www.cnblogs.com/angryjj/p/11324590.html