死磕数据库系列(十五):MySQL 存储过程、自定义函数、事务、流程控制的语法、创建和使用...

2b1f5a37cbb7ce2db34693f4ebca9b7f.gif关注公众号,回复“1024”获取2TB学习资源!

存储过程

语法:

创建存储过程

drop procedure if exists [存储过程名] ;

delimiter [结束标记]
create procedure [存储过程名]([参数1], [参数2] ...)
begin
    [存储过程体(一组合法的sql语句)]
end [结束标记]

delimiter ;

参数,其格式为 [in|out|inout] parameter_name type

in #表示输入参数;
out #表示输出参数;
inout #表示此参数既可以输入也可以输出;
param_name #表示参数名称;
type #表示参数的类型。

delimiter [结束标记] 本身与存储过程的语法无关,用于表示SQL语句的结束。

最后一个命令 delimiter ; 将 分隔符 改回 分号 ,用于告诉SQL引擎,遇到 分号 就可以提交了。

为什么要用 delimiter 定义 结束标记?

因为 SQL引擎 执行时,遇到分号(;)表示一个SQL语句的结束,会把这个SQL进行提交。

但是 存储过程和自定义函数,功能比较复杂,里面有好多条的SQL语句, 我们希望SQL引擎把 这一个存储过程或函数当成一个整体提交 (即原子性),而不是每段SQL语句遇到分号(;)就提交,这就无法保证完整性。

为了解决SQL执行引擎遇到分号提交的问题,就要使用 delimiter 定义一个新的结束标记 ,只不要是分号, SQL引擎就不会提交,这样保证 存储过程和自定义函数 的整体性。

所以,SQL 的 delimiter 的结束标记 不能定义为分号(;),一般使用特殊字符,如$ 、//、/ 等。

begin...end 语句块。 在 sql 语句,如果包含多条语句, 我们需要把多条语句放到 begin...end 语句块中。begin...end 块可以嵌套,同一组的SQL 代码放到 一个 begin...end 块,层层分开, 最大的好处是便于阅读 。 如果仅仅一条sql语句,则可以省略 begin end

begin...end 相当于 java 语言中的 { } 。如果 if 内只有一条SQL时,{ } 可以省略,同样,begin...end 也可以省略。

public int method(int param){
 int result=0;
 if(param==1){    
  result=......
 }else if{param==2}{   
  result=......
 }else{   
  result=......
 }
 return result;
}

public int method(int param){
 int result=0;  
 
 // 当 if 内只有一条SQL时,{ } 可以省略
 if(param==1)  
  result=...... 
 else if{param==2}
  result=......
 else
  result=......
 
 return result;
}

调用存储过程

call [存储过程名]([ proc_parameter [,proc_parameter ...]])

call [存储过程名]

说明:

  • 当无参数时,可以省略括号,不写;

  • 当有参数时,不可省略括号。

存储过程修改 : 修改存储过程,就是删除重建。

删除存储过程: drop procedure [if exists] sp_name

示例
drop table if exists `t_user`;
create table `t_user` (
  `id` int not null auto_increment,
  `name` varchar(20) not null,
  primary key (`id`)
) engine=innodb auto_increment=1 default charset=utf8;
无参数的存储过程
drop procedure if exists myproc1;
delimiter $
create procedure myproc1()
begin   ## 下面只有一条语句,begin end 可以省略
    insert into t_user values(null, 'Jas'),(null, 'Joy');
end $
delimiter ;

调用,并查看结果:
32a39055e5372a9b90e3fff2eb924fc2.png

只有一个 in 参数的存储过程
drop procedure if exists myproc2;

delimiter $
create procedure myproc2(in userId int)
begin
    select name from t_user where id = userId;
end $

delimiter ;

调用,并查看结果:
3d910bbc10f427bfd4d46af37f37ed8f.png

包含 in 参数和 out 参数的存储过程
drop procedure if exists myproc3;

delimiter $
create procedure myproc3(in userId int, out username varchar(20))
begin
    select name into username     # 将查询到的用户名赋值给 username
    from t_user where id = userId;
end $
delimiter ;

调用,并查看结果:
efb1a9aee6de8633b0f589deb678b277.png

  • 创建存储过程 myproc3,包含一个in参数和一个out参数 ;

  • 调用时,传入删除的id 和 全局变量 @username ;

  • select @username 输出结果。

包含 inout 参数的存储过程
drop procedure if exists myproc4;

delimiter $
create procedure myproc4(inout a int)
begin
    set a = a * 2;
end $

delimiter ;

调用存储过程,并查询结果:443985697b1faaf89ad8b92340c6d444.png

附:根据 时间 修改 状态:
delimiter $$

use `exam9` $$  ## exam9 是数据库

drop procedure if exists `updateStatus` $$   ## 如果存在,就删除

create  procedure `updateStatus`()
begin
    update exam set `status`="已结束" where  `status` != "已结束" and  (now() - endtime)>0  ;
    update exam  set `status`="正在答题"  where   `status` != "已结束" and ( now() - starttime)>=0 
    and  (now() - endtime)<=0;
    
    update haulinfo set bigstatus="已结束" where  bigstatus != "已结束" and  (curdate() - bigenddate)>0; 
    update haulinfo set bigstatus="进行中" where (curdate() - bigenddate)<=0 and (curdate() - bigbegindate)>=0;
    
    update exam set bigstatus=(select bigstatus from haulinfo where bigid=exam.bigid);
end $$

delimiter ;
区别:
存储过程的优点:
  • 存储过程 就是把经常使用的 sql语句 或 业务逻辑封装起来,预编译保存在数据库中,当需要的时候从数据库中直接调用,省去了编译的过程;

  • 提高了运行速度;

  • 同时降低网络数据传输量( 不用传一堆sql代码快,而是传一个存储过程名字和几个参数)。

存储过程 与 函数 的区别
  • 一般来说,存储过程实现的功能要复杂一点,而函数的实现的功能针对性比较强。

  • 存储过程可以有返回值也可以没有返回值,而自定义函数必须要返回值,且返回值有且只有一个。

  • 存储过程一般是作为一个独立的部分来执行,而函数可以作为查询语句的一个部分来调用,因此它可以在查询语句中位于 from 关键字的后面。 SQL 语句中不可用存储过程,而可以使用函数。

二、自定义函数 udf(user-defined function )

自定义函数 就像是 abs() 、 concat() 内建函数一样去扩展 mysql 。

所以,udf 是对 mysql 功能的一个扩展。

自定义函数 udf
创建 udf
drop function if exists [函数名];

delimiter [结束标记]
create function [函数名]([参数1], [参数2] ...) returns [返回值类型]
begin 
    [方法体]
    return [返回值];
end [结束标记]

delimiter ;

参数,其格式为param_name type,如username varchar(20)

删除 udf: drop function [函数名]

3udf : select [函数名](param_value, ...)

示例

t_user 表中的数据:
b6639509a419e46e45e5bc18f22b6762.png

无参数的自定义函数

查询 t_user 中的数据行数,并返回。

drop function if exists myfun1;

delimiter $
create function myfun1() returns int
begin
    declare sum int default 0;    # 定义局部变量 sum,默认值为 0
    
    select count(*) into sum    # 将查询的结果赋值给 sum
    from t_user;
    return sum;
end $

delimiter ;

调用结果:
f9332bcb0d0f19c46dbfbcb0ca68e94d.png

有参数的自定义函数
drop function if exists myfun2;

delimiter $
create function myfun2(userId int) returns varchar(20)
begin
 set @username='';    # 定义系统会话变量
 select name into @username    # 将用户名赋值给 username
 from t_user
 where id = userId;
 return @username;
end $

delimiter ;

查看结果:
92c6d8cb9ffa2b881d01f4e47b21c3b9.png

复合结构

语法格式
delimiter //
create function if exist deleteById(uid smallint unsigned) returns varchar(20) 
begin
    delete from t_order where id = uid;
    return (select count  (id) from son);
end //

delimiter ;
delimiter 修改默认的结束符

delimiter // 表示 将默认的结束符由 ; 改为 // ,以后的sql语句都要以 // 作为结尾 。

returns 声明返回值类型

returns varchar(20) 声明 返回值 是 20位长度的字符串 。

returns int 声明返回值 int 。

reurn 定义 返回值

reurn 语句 也包含在begin...end 中。

declare 定义局部变量
declare  var_name[,varname]...date_type [default value];

简单来说就是:

declare 变量1[,变量2,... ]变量类型 [default 默认值]

这些变量的作用范围是在begin…end程序中,而且定义局部变量语句必须在begin…end的第一行定义

示例:
delimiter //
create function addNum(x smallint unsigned, y smallint unsigned) returns smallint
begin
    declare a, b smallint unsigned default 10; ### 定义局部变量
    set  a = x, b = y;
    return a+b;
end //

delimiter ;

上边的代码只是把两个数相加,当然,没有必要这么写,只是说明局部变量的用法,还是要说明下:这些局部变量的作用范围是在 begin...end 程序中

变量

系统变量
全局变量 (global)

作用域:针对于所有会话(连接)有效,但不能跨重启。重启后,配置失效。

# 查看所有全局变量
show global variables;

# 查看满足条件的部分系统变量
show global variables like '%char%';

# 查看指定的系统变量的值
select @@global.autocommit;

# 为某个系统变量赋值
set @@global.autocommit=0;
set global autocommit=0;
会话变量(默认 session )

作用域:针对于当前会话(连接)有效
系统变量,如果不加 globalsession ,则默认就是 session 。

# 查看所有会话变量
show session variables;  ## 等价于 show variables;

# 查看满足条件的部分会话变量
show session variables like '%char%';

# 查看指定的会话变量的值
select @@autocommit;
select @@session.tx_isolation;

# 为某个会话变量赋值
set @@session.tx_isolation='read-uncommitted';
set session tx_isolation='read-committed';
自定义变量
用户变量 (全局的变量)

作用域: 用户变量 在当前连接(即当前会话)中都有效。

用户变量的特点:
  • 不需要声明,直接使用,

  • 前后数据类型可以不一样。

示例:

同一个用户变量,前后可以接收不同类型的赋值。

age 是 int
name 是varchar(20)

set @tmpVal = age;   ## 将age的值赋给 @tmpVal
set @tmpVal = name;  ## 将name的值赋给 @tmpVal
声明并初始化:
set @变量名=值;
set @变量名:=值;
select @变量名:=值;
赋值:
## 方式一:一般用于赋简单的值
set 变量名=值;
set 变量名:=值;
select 变量名:=值;

## 方式二:一般用于赋表 中的字段值
select 字段名或表达式 into 变量
from 表;
使用:
select @变量名;
局部变量

作用域: 局变变量在 begin end 语句中有效,超过范围即失效。

声明:
declare 变量名 类型 【default 值】;
declare name varchar(20) ;
declare num int default 0;

局部变量 必须在 begin end 的第一行 。

赋值:
# 方式一:一般用于赋简单的值
set 变量名=值;
set 变量名:=值;
select 变量名:=值;

# 方式二:一般用于赋表 中的字段值
select 字段名或表达式 into 变量
from 表;
使用:
select 变量名
用户变量与 局部变量的区别:
c6982621c65922d1a1bcceb36c823889.png

事务和回滚点

事务
set autocommit=0;    ## 1、取消自动提交
start transaction;   ## 2、开启事务

要执行的操作

commit;     ## 3、提交事务
rollback;   ## 4、回滚事务
存储过程和函数使用事务的格式:
begin  
 set autocommit=0;    ## 取消自动提交
 start transaction;   ## 开启事务
  
要执行的操作
 
 commit;   ## 提交事务
end ;
保存点

在事务中,设置保存点,当回滚时,能回滚到这个保存点,但是保存点之前的执行不会回滚。

set autocommit=0;    ## 取消自动提交
start transaction; 
..... ## SQL语句
savepoint aa; ## 设置保存点,aa 是自定义的名称,保持唯一
..... ## SQL语句
rollback to aa ## 回滚到保存点。 但是保存点前面执行的SQL执行依然有效。

流程控制

存储过程 和 函数 中可以使用 流程控制语句 来 控制SQL 的执行。

mysql中可以使用 if 、case 、loop、leave、iterate 、repeat和 while 语句 来进行流程控制。

每个流程中可能包含一个单独语句,或者是使用 begin...end 构造的复合语句,构造可以被嵌套。

if 函数
语法:
if(条件,值1,值2)

如果条件成立,则返回 值1,否则返回 值2

特点:可以用在任何位置。e007ce5b22b44a0c3129a358b9f26c70.png

if 条件判断

根据是否满足条件,将执行不同的语句。

特点:
与 if 函数 的不同,这个if 条件只能用在 begin end 中 !!

if 语法:
if  表达式1 then 语句1;
elseif 表达式2 then 语句2;
...
else 语句n;
end if;
参数说明:

特点:只能用在 begin end 中 !!

注意: mysql还有一个if()函数,不同于这里描述的 if 语句。

if 示例:

if age>20 then 
 set @count1=@count1+1;  
elseif age=20 then 
 set @count2=@count2+1;  
else 
 set @count3=@count3+1;  
end if;

说明:

  • 根据age与20的大小关系来执行不同的set语句。

  • 如果age值大于20,那么将count 1的值加1;

  • 如果age值等于20,那么将count 2的值加1;

  • 其他情况将 count3 的值加 1 。

  • 最后,if 语句都需要使用 end if 来结束。

case when 条件判断

case when 也用来进行条件判断,其可以实现 比 if 更复杂的条件判断。

case when 语法:

类似于 switch

case 表达式
 when 值1 then 结果1或语句1  ## 如果是语句,需要加分号
 when 值2 then 结果2或语句2  ## 如果是语句,需要加分号
 ...
 else 结果n或语句n          ## 如果是语句,需要加分号
end 【case】   ## 如果是放在begin end中,则结尾处需要加上case,如果放在select后面不需要
说明:
  • 如果是在 begin end 中,则需要在每个 语句后面加 分号,case 结尾处加 case

  • 如果放在 select 后面,则每个语句后面不需要加分号, case 结尾处不需要加 casea0e3b20004fd1832d7cfda81e6f7a591.png

case when 语法2:
case 
 when 表达式1 then 结果1或语句1   ## 如果是语句,需要加分号 
 when 表达式2 then 结果2或语句2   ## 如果是语句,需要加分号
 ...
 else 结果n或语句n   ## 如果是语句,需要加分号
end 【case】           ## 如果是放在begin end中,则结尾处需要加上case,如果放在select后面不需要
说明:
  • 语法1 的when 后面是值,

  • 语法2 的when 后面是 表达式,可以进行区间的判断,

这个两个语法最大的区别。

示列:
d2ef3233043fa2aa93dc656d468dc38a.png
loop 循环

loop 可以使某些特定的语句重复执行,实现一个简单的循环。

但是 loop 本身没有停止循环的语句,必须使用 leave、iterate 等才能停止循环。

loop 语法:
[begin_label:] loop 
 statement_list 
end loop [end_label]
参数说明:
  • 1.begin_label 、end_label 分别表示 循环开始 和 结束的标志,这两个标志必须相同,而且都可以省略;

  • 2.statement_list 表示需要循环执行的语句。

loop 示例
add_num: loop  
 set @count=@count+1;  
end loop add_num ;
说明:
  • 循环执行 count 加1的操作。

  • 因为没有跳出循环的语句,这个循环成了一个死循环。

  • loop 循环都以 end loop 结束。

leave 跳出循环(break)

leave 用于跳出循环。leave 用于 loop、repeat、while ,中断 并跳出循环。

leave 语法:
leave label
参数说明:

label 表示 循环的标志。

leave 示例:
add_num: loop 
 set @count=@count+1; 
 if @count=100 then 
  leave add_num ; 
end loop add_num ;

循环执行 count 加1的操作。当 count 的值等于100时,则leave语句跳出循环。

iterate 跳出本次循环(continue)

iterate 也是跳出循环。但是,iterate 语句是跳出本次循环,然后直接进入下一次循环。

iterate 用于 loop、repeat、while 语句跳过本次循环。

iterate 语法:
iterate   label
参数说明:

label 表示循环的标志。

iterate 示例:
add_num: loop
    set @count=@count+1;
    if @count=100 then
     leave add_num ;
    else if mod(@count,3)=0 then
     iterate add_num;
    select  from employee ;
end loop add_num ;

说明:

  • 循环执行 count 加1的操作。

  • 当 count 值为100时结束循环。

  • 如果 count 的值能够整除3,则跳出本次循环,不再执行下面的select语句。

leave 和 iterate 的区别
相同点:

leave 和 iterate 都用来跳出循环语句,但两者的功能是不一样的。

不同点:
  • leave 是跳出整个循环,然后执行循环后面的程序。

  • iterate 是跳出本次循环,然后进入下一次循环。

repeat (先)循环

repeat 是有条件控制的循环。当满足特定条件时,就会跳出循环语句。

repeat 语法:
repeat 
 statement_list 
 until search_condition    # until 后面没有分号(;),否则 报错
end repeat;
参数说明:
  • 1.statement_list 表示 循环的执行语句;

  • 2.search_condition 表示 结束循环的条件,满足该条件时循环结束。

repeat 示例:
repeat 
 set @count=@count+1; 
 until @count=100 
end repeat ;

循环执行count 加1的操作。

当 count 值为10 0时 结束循环。

repeat循环都用end repeat结束。

while (先判断,再)循环

while 也是有条件控制的循环语句。但while 和 repeat 是不一样的。

while 是当满足条件时,执行循环内的语句。

while 语法:
while   search_condition do 
 statement_list 
end while ;
参数说明:
  • 1.search_condition 表示 循环执行的条件,满足该条件时循环执行;

  • 2.statement_list 表示 循环的执行语句。

while 示例:
while   @count<100 do 
 set @count=@count+1; 
end while ;
  • 循环执行count 加1的操作。

  • 如果 count 值小于100时执行循环;

  • 如果 count 值等于100了,则跳出循环。

  • while 循环需要使用end while 来结束。

循环示例

loop 循环的示例

delimiter $$

drop procedure if exists `sp_testloop` $$

create  procedure `sp_testloop`(
 in p_number int, #要循环的次数 
 in p_startid int #循环的起始值
)
begin  
    declare v_val int default 0;  
    set v_val=p_startid;

    loop_label:  loop   #循环开始
        set v_val=v_val+1; 
         
        if(v_val>p_number)then
            leave  loop_label;  # 终止循环
        end if;
    end loop; 
    select concat('testloop_',v_val) as tname;
end $$

delimiter ;

call sp_testloop(1000,0);

while循环的示例

delimiter $$

drop procedure if exists `sp_test_while`$$

create  procedure `sp_test_while`(
 in p_number int, #要循环的次数
 in p_startid int #循环的起始值
)
begin  
 declare v_val int default 0;  
 set v_val=p_startid;
 outer_label:  begin  #设置一个标记
  while v_val<=p_number do  
   set v_val=v_val+1;  
   if(v_val=100)then
    leave  outer_label;  #满足条件,终止循环,跳转到 end outer_label标记
   end if;
  end while;
  
  select '我是while外,outer_label内的SQL';## 这句SQL在outer_label代码块内,所以level后,这句SQL将不会执行;
 
 #只要是在outer_label代码块内 任意位置 Leave outer_label,那么Leave后的代码将不再执行
 end outer_label;
 select concat('test',v_val) as tname;
end$$

delimiter ;

call sp_test_while(1000,0);

repeat 循环的示例

delimiter $$
drop procedure if exists `sp_test_repeat`$$

create  procedure `sp_test_repeat`(
 in p_number int, #要循环的次数
 in p_startid int #循环的起始值
)
begin  
 declare v_val int default 0;  
 set v_val=p_startid;

 repeat  #repeat循环开始
  
  set v_val=v_val+1; 
  until v_val > p_number   #终止循环的条件,注意这里不能使用';'分号,否则报错     
    
 end repeat; #循环结束 
  
 select concat('test',v_val) as tname;
end$$

delimiter ;

call sp_test_repeat(1000,0);

来源:https://xiaojin21cen.blog.csdn.net/

article/details/105596615

9da6642bcd53ee4c5480114499555ccd.png

a96e05643195b1aba2da4fe9e46793d0.png

推荐阅读 点击标题可跳转

一个高性能、无侵入的性能监控工具,有点东西!

不愧是微软出品的工具!逆天啊。。。

 万字总结!40 道 Nginx 经典面试题!(附答案)

使用 VPN 你一定要了解这几个真相!

这些一夜爆火的国产软件,却被误以为是外国佬研发的

疯狂涨价!甲骨文推出全新定价模式,想钱想疯了

卸载 Notepad++!事实已证明,它更牛逼…

死磕数据库系列(十四):MySQL 锁机制详解

f63cf369432d59d4aac0578aa7ba10d5.png

转发 关注

点亮下方“在看”图标

更多人看到

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值