存储过程其实完全可以类比成高级语音里的方法和函数。这样看起来就不会抽象,也有个类比。通常函数体都会定义入参、出参,函数内部会定义该函数的计算过程,这里可能会有加减乘除、循环遍历、等等操作。procedure自然也是如此,因此高级语言中的函数里需要的那些东西,这里也是需要的,比如在函数里定义一个局部变量,在函数内引用静态、成员变量,procedure里也有这些操作。再比如函数里可以写for循环、while循环、异常抓取,procedure里也是可以的。procedure拥有了这些,才让存储过程可以解决一些普通sql处理不了的问题。
上面说过procedure也是支持入参出参的吗,先来看下procedure的标准写法吧:
delimiter $$
create procedure sp_name([proc_parameter[,...]])
[characteristic...] -- 这块目前没啥用,一般不会使用
begin
...
end $$
delimiter ;
对上面的写法略作解读:
下面是使用in的一个简单场景:
-- 删除存储过程
drop procedure if exists in_procudure;
-- 创建存储过程
delimiter $$
create procedure if not exists in_procudure(in param integer)
begin
select param;
end $$
delimiter ;
-- 调用存储过程
call in_procudure(1);
下面是一个使用out的一个简单场景:
这里出现了一些上面没有的内容,作下说明,
drop procedure if exists out_procedure;
delimiter $$
create procedure if not exists out_procedure(in param_one integer,out param_out varchar(50))
begin
select room_name into param_out from room where id = param_one;
end $$
delimiter ;
call out_procedure('1',@param_three);
select @param_three;
下面是inout的简单实例:
这里又出现了一些上面没有的内容,作下说明:
drop procedure if exists inout_procedure;
delimiter $$
create procedure inout_procedure(inout param_one varchar(100))
begin
select sale_price into param_one from room where id = param_one;
end $$
delimiter ;
set @id = '1';
call inout_procedure(@id);
select @id;
上一章节说的是存储过程的基本写法,这一节会对其进行扩充,在基础写法的基础上增加变量的操作,类比高级语言可以发现,复杂的程序离不开成员变量、局部变量的配合使用,procedure也是一样。这里的局部变量指的也是函数体内部的变量,这里的用户变量可以类比成员变量。
关于局部变量有以下使用规定:
下面是局部变量的简单使用:
drop procedure if exists jubu_procedure;
delimiter $$
create procedure if not exists jubu_procedure()
begin
declare jubu_param varchar(50) default null;
set jubu_param = '我是局部变量';
select jubu_param;
end $$
delimiter ;
call jubu_procedure();
局部变量是只能在procedure中使用的,下面要说的用户变量和系统变量都是mysql本来就支持的,和procedure没有必然联系,当然他们也是可以在procedure中使用的了。用户变量就是当前用户在一次数据库访问中创建的变量,该变量只在一次与数据库的会话中有效,会话结束用户变量的声明周期也就结束。
关于用户变量的使用规定:
下面是一个使用用户变量的例子:
drop procedure if EXISTS user_procedure;
delimiter $$
create procedure if not exists user_procedure(inout param_one varchar(50))
begin
declare jubu_param varchar(50) DEFAULT '';
select param_one;
set jubu_param = param_one;
select jubu_param;
set @user_param = jubu_param;
select @user_param;
end $$
delimiter;
set @param_two = '测试用户变量';
call user_procedure(@param_two);
系统变量是mysql启动时自动设置的,当然我们也可以
下面是系统变量的写法(一般在存储过程能用到系统变量的地方少一些):
set @@global.sort_buffer_size = 262144;
select @@global.sort_buffer_size;
这里的条件处理其实就像是java里的try…catch,都是为了定义发生某种异常场景时程序该如何处理。这么一类比可能就会非常清晰了,一起看下存储过程中条件处理的定义吧。
下面是条件处理的标准写法(需要写在procedure里):
declare handler_type handler for condition_value[,...] sp_statement;
-- 参数解释
handler_type:
continue -- 忽略错误,继续执行
|exit -- 碰到错误,退出
|undo -- 这个值目前没有正式使用,回滚之前的操作
condition_value:
sqlstate[value] sqlstate_value -- 直接声明异常码
|condition_name -- 自定义的条件
|sqlwarning -- 涵盖所有以01开头的sqlstate的状态码
|not found -- 涵盖所有以02开头的sqlstate的状态码
|sqlexception -- 涵盖所有非sqlwarning、not found以外的所有异常
|mysql_error_code-- error类型的异常
可以看到其实条件处理的语句写起来很简单
我们可以看到在条件处理时,他的codition_value 可以是 condition_name,那这个condition_name是啥呢,其实就是我们自己定义的条件。就是说我们先定义条件,条件里面声明异常场景,处理时直接引用这个条件就行。在笔者看来这是把条件处理搞复杂了,我们完全可以使用条件处理直接声明异常场景,为甚么非要多写个条件声明呢,笔者建议直接使用条件处理。
条件声明的标准写法如下:
declare condition_name condition for condition_value;
下面是一条主键冲突的异常场景的处理
drop procedure if exists condition_procedure;
delimiter $$
create procedure if not exists condition_procedure()
begin
declare continue handler for sqlstate '23000' set @ex = 1;
set @msg = '';
insert into room values (1,'105',2000);
if @ex=1 then set @msg = '异常了';
else set @msg = '无异常';
end if;
end $$
delimiter;
call condition_procedure();
select @msg;
若是没有加条件处理的语句,那肯定是要报异常的,下面再看下上面的操作修改成使用条件定义+条件处理的方式的写法
delimiter $$
create procedure if not exists cond_procedure()
begin
declare primaryError condition for sqlstate '23000';
declare continue handler for primaryError set @ex = 1;
insert into room values (1,'105',2000);
if @ex = 1 then set @msg = '主键异常了';
else set @msg = '无异常';
end if;
end $$
delimiter ;
call cond_procedure();
select @msg;
可以说之类介绍的每一个点都是使用存储过程解决实际问题必不可少的要素,流程控制也是如此,这里的前两个是做条件判断的有些相似,后面三个则都是循环控制的,他们也有些相似。其实和java里的都类似,类比下即可。
格式如下:
if ... then ...;
[elseif ... then ...;]
[else ...;]
end if;
使用举例:
drop procedure if exists if_procedure;
delimiter $$
create procedure if not exists if_procedure()
begin
declare var_param integer default 0;
if var_param = 1 then set @msg = '局部变量是1';
elseif var_param = 2 then set @msg = '局部变量是2';
elseif var_param = 3 then set @msg = '局部变量是3';
else set @msg = '局部变量不是123';
end if;
select @msg;
end $$
delimiter ;
call if_procedure();
如上所示,需要注意的是,存储过程每一个句子的结束都是需要分号结尾的,这个不能少,因为他也是一句一句执行嘛,不告诉他从哪里到哪里他也解析不出来啊。其次还需要注意if … end if;必须成对出现。
有sql基础的话,那这个就好写了,因为这里的语法与sql中一模一样,就会每行结束需要加分号就是。
标准写法有两种,第一种如下:
case ...
when ... then ...;
when ... then ...;
else ...;
end case;
第二种如下:
case when ... then ...;
when ... then ...;
when ... then ...;
else ... ;
end case;
这两种的写法,在sql中应该都挺常用的,应该都不陌生,这里就简单举个粒子:
drop procedure if exists case_procedure;
delimiter $$
create procedure if not exists case_procedure()
begin
declare var_param varchar(20) default '张三';
-- 第一种case 用法
case var_param
when '张三' then set @msg = '他是张三';
when '李四' then set @msg = '他是李四';
when '王五' then set @msg = '他是王五';
else set @msg = '未知人';
end case;
select @msg;
-- 第二种case 用法
case
when var_param = '张三' then set @msg = '第二种张三';
when var_param = '李四' then set @msg = '第二种李四';
-- 这里的when后面可以跟任何条件
when true = false then set @msg = '这里恒不等';
else set @msg = '未知人';
end case;
select @msg;
end $$
delimiter ;
call case_procedure();
对比下两种写法,会发现第二种更灵活些,第一种写法稍简单,怎么使用其实差别不大。
后面这三个就是循环的使用了,其实从上面的一些粒子我们应该能够总结出一些规律了,比如说 他们格式都是这种 ==关键字… end 关键字;==下面的这三种循环的流程控制也是如此,都会遵循这个规律,看下loop的标准写法吧:
[begin_label:]loop
...
end loop [end_label];
看上面的标准写法我们不难看出,loop并没有声明怎么退出循环体,类比java,会发现有点类似于java里的while(true),这个循环会一直执行下去。那怎么才能退出去呢,下面就要说下iterate与leave了,他们都是用来退出循环控制的语句。
使用leave退出整个循环,当前循环会完全退出。类似java语言里的breake。
使用leave结合loop的简单例子:
drop procedure if exists loop_procedure;
delimiter $$
create procedure if not exists loop_procedure()
begin
declare int_param int default 10;
declare int_var int default 0;
label:loop
if int_var = int_param then leave label;
else set int_var = int_var + 1;
end if;
end loop label;
set @msg = int_var;
select @msg;
end $$
delimiter ;
call loop_procedure();
注意:
使用iterate退出当前循环,继续执行下一层的循环,类似java语言里的continue。
使用iterate结合loop的简单例子:
drop procedure if exists loop_procedure;
delimiter $$
create procedure if not exists loop_procedure()
begin
declare int_param int default 10;
declare int_var int default 0;
-- loop 的开始和结束标签都是可以省略的
label:loop
if int_var = int_param then set int_var = int_var + 1;iterate label;
elseif int_var = 20 then leave label;
else set int_var = int_var + 1;
end if;
end loop label;
set @msg = int_var;
select @msg;
end $$
delimiter ;
call loop_procedure();
下面是要说repeat
下面是标准写法:
[begin_label:]repeat
until ... end repeat end_label;
简单例子:
delimiter $$
create procedure if not exists repeat_procedure()
begin
declare var_int int default 10;
label:repeat
if var_int < 10 then set var_int = var_int + 1;
end if;
until var_int = 10 end repeat label;
set @msg = var_int;
select @msg;
end $$
delimiter ;
call repeat_procedure();
while … do 其实与repeat很是类似,都是自带退出条件,不像loop那样需要我们在里面使用条件判断,不过他们还是有一些区别的,while是满足条件才会执行,反过来说其实就是不满足就退出呗,repeat则是满足则退出。不过真正使用时,都是类似的差别不大。
while…do的标准下发如下:
[begin_label:]while condition do ...
end while [end_label];
一个简单的例子:
drop procedure if exists while_procedure;
delimiter $$
create procedure if not exists while_procedure()
begin
declare var_int int default 1;
label:while var_int <100 do
set var_int = var_int + 1;
end while label;
set @msg = var_int;
select @msg;
end $$
delimiter ;
call while_procedure();
流程控制+变量+条件处理,这个程序组织起来你会发现好像就是缺了点啥。回想下使用java写程序时会发现,每次遍历其实我们都是会去遍历一个集合、数组、流等,那procedure里怎么遍历这个,之前好像都没有提到过。那这里就要用到光标了,光标的作用其实就可以类比成流,使用前需要将数据放入光标,这是定义光标的过程;使用时需要打开光标,使用后需要关闭光标,并且光标的数据不受影响。是不是很像一个流呢。
下面是光标的标准语法:
declare cursor_name cursor for select_statement; -- 定义光标的数据
open cursor_name; -- 打开光标,只有打开光标才能操作里面的数据
fetch cursor_name into var_name[,...]; -- 将光标中的数据取出来放在变量里进行操作
close cursor_name; -- 关闭光标,光标使用完必须关闭
上面已经介绍了,光标的使用其实有这四步,每一步都是不可获取的,声明光标(定义数据),打开光标(操作数据做准备),获取光标(拿到数据操作),关闭光标(使用完关闭,就是操作流时一样)。
我们假设一个场景,然后去体验下这个光标:
有一个room表,有三列,现在想要将列sale_price的价格进行累加,我们可以这么写:
drop procedure if exists cursor_procedure;
delimiter $$
create procedure if not exists cursor_procedure()
begin
declare var_price double default 0;
declare price_cursor cursor for select sale_price from room;
declare exit handler for not found close price_cursor;
set @sum_price = 0;
open price_cursor;
repeat
fetch price_cursor into var_price;
set @sum_price =@sum_price + var_price;
until 0 end repeat;
close price_cursor;
end $$
delimiter ;
call cursor_procedure();
select @sum_price;
这样也就正常求出一个和了,其实和我们的sum()函数有些类似。这里使用了repeat对fetch中的数据进行循环取数,同时定义了一旦数据被取完就会关闭光比并退出。
存储过程里面其实东西不算多,相比于高级语言里已经少了一些了,不过这些已经能解决大部分问题了,这里总结下,先是参数(in、out、inout)在后面就是条件处理了(condition、handler)在后面又是流程控制(if…else,case…when,leave、iterate、loop、repeat、while)在后面就是光标了(cursor),掌握好这些存储过程也就没有死点了。