MySql存储过程详解

存储过程

在一些语言中,比如pascal,有 “过程” 和 “函数” 的概念。

  • 过程:封装了若干条语句,调用时,这个封装体集中执行

  • 函数:是一个有返回值的过程

  • 过程:是一个没有返回值的函数

我们将若干条数据封装起来 — 过程

再将过程存储到数据库中 — 存储过程

mysql没有匿名过程(使用后直接丢掉)

创建存储过程

create procedure procedureName()
begin
  sql 语句集
end//

查看已有的存储过程

show procedure status;

调用存储过程

call procedureName();

存储过程是可以变成的,意味着存储结果可以使用变量、表达式、控制结构,那么存储过程可以完成复杂的功能。

引入变量

定义变量的格式: declare 变量名 变量类型 [default 默认值]

drop procedure if exists p_test1//
create procedure p_test1()
begin
    declare age int default 18;
    declare height int default 180;

    select concat('年龄是', age, ' 身高是', height) from dual;
end //

传参

/*
打印矩形的类型  瘦、胖、方
*/
delimiter //
drop procedure if exists p_test2//
create procedure p_test2(width int, height int)
  begin
    /*存储过程不能有返回值*/
    if width > height then
      select '你很胖' from dual;
    elseif width < height then
      select '你很瘦' from dual;
    else
      select '你很方' from dual;
    end if;
  end//
delimiter ;
call p_test2(100, 100);

while循环:

/*
计算1到100的和
*/
delimiter //
drop procedure if exists p_test3;
create procedure p_test3()
  begin
    declare total int default 0;
    declare num int default 1;

    while num <= 100 do
      -- mysql中等于也是判断,所以推荐:=区分
      set total := num + total;
      set num := num + 1;
    end while ;

    select total from dual;
  end //
delimiter ;

call p_test3();

in, out, inout:

---------------- 使用 in和out参数改造
delimiter //
drop procedure if exists p_test4;
# in型参数可以接受参数
# out类型可以输出参数
create procedure p_test4(in n int, out total int)
  begin
    declare num int default 1;
    set total := 0;

    while num <= n do
      set total := num + total;
      set num := num + 1;
    end while ;

    select total from dual;
  end //
delimiter ;

# @开头是输出变量
call p_test4(100, @total);
select @total;

---------------- inout参数
/*
给年龄涨10岁
*/
delimiter //
drop procedure if exists p_test5;
create procedure p_test5(inout age int)
  begin
    set age := age + 10;
  end //
delimiter ;
set @currentAge := 18;
call p_test5(@currentAge);
select @currentAge from dual; # 28

case结构

/*
case
*/
delimiter //
drop procedure if exists p_test6;
create procedure p_test6(in i int)
  begin
    case i
      when 1 then select 'one';
      when 2 then select 'two';
    else select 'zero';
    end case;
  end //
delimiter ;
call p_test6(1); # one

repeat结构

/*
计算1到100的和,使用repeat
 */
delimiter //
drop procedure if exists p_test7;
create procedure p_test7()
  begin
    declare total int default 0;
    declare num int default 1;
    repeat
      set total := total + num;
      set num := num +1.;
    until num > 100
    end repeat;
    select total;
  end //
delimiter ;
call p_test7();

游标

/*
游标 cursor
1条sql查询语句,往往会查出多条记录。取出资源的句柄就是游标
游标每次可以取出一条记录,通过循环可以依次将资源取出,并做出处理

创建游标:
declare 游标名 cursor for select statement;
打开游标:
open 游标名;
取出一个资源,并将游标后移一位:
fetch 游标名 into var1, var2, var3;
关闭游标:
close 游标名称;
*/

/*
使用游标取出商品表的第一条记录的内容,并打印出来
*/
delimiter //
drop procedure if exists p_test8;
create procedure p_test8()
  begin
    # 定义变量,用来接收fetch收到的列值
    declare row_id int;
    declare row_name varchar(20);
    declare row_stock int;
    # 定义游标
    declare get_goods cursor for select id, name, stock from goods;
    # 开启游标,准备遍历
    open get_goods;
    # 获取一条资源
    fetch get_goods
    into row_id, row_name, row_stock;
    select concat('id:', row_id, ' name:', row_name, ' stock:', row_stock);

    # 处理结束,关闭游标
    close get_goods;
  end //
delimiter ;

call p_test8();

游标循环:

/*
上面的游标只fetch了一条记录,要想fetch所有记录,要进行游标循环
游标循环共有一下几种实现方式:
1. select count(*) 计算记录条数并赋值给变量循环
2. 使用越界标识
*/

-- 使用select count(*) 计算记录数
delimiter //
drop procedure if exists p_test9;
create procedure p_test9()
  begin
    # 定义变量,用来接收fetch收到的列值
    declare row_id int;
    declare row_name varchar(20);
    declare row_stock int;
    # 获取记录条数
    declare counts int default 0;
    # 定义游标
    declare get_goods cursor for select id, name, stock from goods;
    select count(*) into counts from goods;
    # 开启游标,准备遍历
    open get_goods;
    while counts > 0 do
      fetch get_goods into row_id, row_name, row_stock;
      set counts := counts-1;
      select concat('id:', row_id, ' name:', row_name, ' stock:', row_stock);
    end while ;
    # 处理结束,关闭游标
    close get_goods;
  end //
delimiter ;

call p_test9();

-- 使用越界标识符
delimiter //
drop procedure if exists p_test10;
create procedure p_test10()
  begin
    # 定义变量,用来接收fetch收到的列值
    declare row_id int;
    declare row_name varchar(20);
    declare row_stock int;
    # 定义越界标识符 1 代表仍然有数据, 0代表没有数据
    declare has int default 1;
    # 定义游标
    declare get_goods cursor for select id, name, stock from goods;
    # 定义游标监控,当下一条数据没有被发现的时候,将has设置为0
    declare continue handler for not found set has := 0;
    # 开启游标,准备遍历
    open get_goods;
    while has <> 0 do
      fetch get_goods into row_id, row_name, row_stock;
      select concat('id:', row_id, ' name:', row_name, ' stock:', row_stock);
    end while ;
    # 处理结束,关闭游标
    close get_goods;
  end //
delimiter ;

call p_test10();

# 注意!!!!!!!!!!
# 这里结果会多取一条,这是为什么?
# !!!!!!!!!!!!!

delimiter //
drop procedure if exists p_test10;
create procedure p_test10()
  begin
    # 定义变量,用来接收fetch收到的列值
    declare row_id int;
    declare row_name varchar(20);
    declare row_stock int;
    # 定义越界标识符 1 代表仍然有数据, 0代表没有数据
    declare has int default 1;
    # 定义游标
    declare get_goods cursor for select id, name, stock from goods;
    # 定义游标监控,当下一条数据没有被发现的时候,将has设置为0
    declare continue handler for not found set has := 0;
    # 开启游标,准备遍历
    open get_goods;
    while has <> 0 do
      fetch get_goods into row_id, row_name, row_stock;
      select concat('id:', row_id, ' name:', row_name, ' stock:', row_stock);
    end while ;
    # 处理结束,关闭游标
    close get_goods;
  end //
delimiter ;

call p_test10();

/*
这里goods表中有两条数据,问题就出在第三次repeat上
当执行第四次repeat时,fetch->没数据->not found->set has = 0 -> continue -> 继续执行下面的sql语句:
select concat('id:', row_id, ' name:', row_name, ' stock:', row_stock);
所以最后一行被取出两次。

解决办法:
保证 not found 发生后,后面的语句不执行

声明处理的handler为 exit 即可达到目的:

declare exit handler for not found set has := 0;

exit和continue的区别:
exit触发后,后面的语句不再执行。
 */

除了 exit,continue 外,还有一种 undo handler。

undo handler 是出发后,前面的语句进行撤销,但是目前mysql并不支持。

最后更新于