SQL 存储过程和函数的对比、变量、条件和处理程序、游标、流程控制详解+代码示例

1. 存储过程和函数在实际项目中的使用

存储过程和函数是SQL中非常实用的功能,它们可以大量简化SQL代码和优化SQL效率,但是在实际项目中应该尽量少用存储过程和函数

理由如下:

  1. 移植性差。例如在MySQL中的存储过程移植到SQL Sever上就不一定可以用了。
  2. 调试麻烦。目前没有专门为SQL开发的用于调试的IDE,无法像VS和Eclipse一样有明确的报错提示。
  3. 扩展性不高。存储过程是一种面向过程的方法,面对复杂的业务逻辑,过程化的处理会很吃力。

所以在大型项目中应该尽量少使用甚至不使用存储过程和函数。

2. 存储过程与函数的比较

共同点

  1. 调用时,只需要提供存储过程名或者函数名,以及参数信息,而无需将SQL语句发送到服务器,节省了网络开销。
  2. 可以重复使用。减少数据库开发人员和应用程序开发人员的工作量。
  3. 可以增强数据的安全访问控制。可以设定用户的执行权限。

不同点

  1. 函数必须有且仅有一个返回值,且必须指定返回值数据类型(字符串/数值)。存储过程可以没有返回值,也可以有返回值,甚至可以有多个返回值,且所有的返回值需要使用out或者inout参数定义。
  2. 函数体内可以使用select into语句为某个变量赋值,但不能使用select语句返回结果或结果集。存储过程则没有这方面的限制,存储过程甚至可以返回多个结果集。
  3. 函数可以直接嵌入到SQL语句中,而且函数可以用于扩展标准的SQL语句。存储过程一般需要单独调用,并不会嵌入到SQL语句中使用,调用时需要使用call关键字。
  4. 函数中的函数体限制比较多,比如函数体内不能使用打开、开始或结束事务的语句,如start transaction、commit、rollback或者set autocommit=0等语句,不能在函数体内使用预处理SQL语句。存储过程的限制相对就比较少,基本上所有的SQL语句或MySQL命令都可以在存储过程中使用。
  5. 应用程序调用函数时,通常将函数添加到SQL语句中进行调用。应用程序调用存储过程时,必须使用call关键字进行调用,如果应用程序希望获取存储过程的返回值,就必须给存储过程的out参数或者inout参数传递SQL会话变量,才能通过该会话变量获取存储过程的返回值。

3. 变量的定义和使用

3.1. 变量的定义

3.1.1. 定义局部变量 declare

关键字declare用于在存储过程或者函数的begin end内部定义一个局部变量,作用范围在begin到end语句块之间。

语法如下:

declare [变量名1],[变量名2],...,[变量名n] [数据类型] [default [默认值]];

如语法所示,在declare关键字后面要指定变量名。变量名要注意尽量别使用关键字,不然容易出错。
指定变量的数据类型及其大小,变量可以用任何MySQL的数据类型,如int,varchar, datatime等。
定义出的变量初始值为Null,可以通过使用default关键字为变量指定默认值。

例如:

create procedure test(in i int)
begin
	declare username varchar(20); -- 定义了一个varchar类型的局部变量username
	declare adate, another date; -- 定义了两个data类型的局部变量username
  	declare total integer default 0; -- 定义一个varchar类型的局部变量username
end
3.1.2. 定义全局变量 set @

定义全局变量可以使用set语句,语法如下:

set @[变量名] = [];

定义全局变量时,sql会自动判断值类型,这种变量要在变量名称前面加上“@”符号,表示一个会话变量,在会话的整个生命周期结束之前它都是有作用的。

例如:

set @var = 7; -- 定义一个值为7的全局变量var
set @username = '西瓜'; -- 定义一个值为'西瓜'的全局变量username

3.2. 变量值的修改

3.2.1. 修改变量的值 set select

set关键字用于修改变量的值,例如:

create procedure test(in i int)
begin
	declare total integer default 0; -- 定义局部变量total,初始化为0;
	set @var = 5; -- 定义全局变量var,初始化为7
	
	set total = 1; -- 将局部变量total的值修改为1
	set @var = 25; -- 将全局变量var的值修改为3
end

select关键字也可以用来修改变量的值,例如:

create procedure test()
begin
	declare total integer default 0; -- 定义局部变量total,初始化为0;
	select 10 into total; -- 将局部变量total的值修改为10
	select total; -- 返回total
end
3.2.2. 将查询结果赋给变量 select into

在存储过程或者函数的处理过程中,通常需要获取表中查询到的值,这时可以使用select…into…语句来获取查询结果并赋值给局部变量,例如:

create procedure test()
begin
	declare total integer default 0; -- 定义局部变量total,初始化为0;
	
	-- 从表prot_user中找到user_name为ghj的记录,将该记录的mobile赋值给total
	select prot_user.mobile into total 
	from prot_user
	where prot_user.user_name="ghj";
	
	select total; -- 返回total
end

3.3. 变量值的作用域

一个变量有自己的范围(作用域),它用来定义它的生命周期。如果在存储过程中声明一个变量,那么当达到存储过程的end语句时,它将超出作用范围,因此在其它代码块中无法访问。

如果在begin…end语句块内声明一个变量,那么如果语句执行到了end,就超出了作用范围。可以在不同的作用域中声明具有相同名称的两个或多个变量,因为变量仅在自己的作用域中有效。

但是,在不同范围内声明具有相同名称的变量不是很好的编程习惯。需要注意的是,以@符号开头的变量是会话变量。直到会话结束前它都是可用的和可访问的。

4. 条件和处理程序的定义

在高级编程语言中为了提高语言的安全性,往往会提供异常处理机制。对于sql而言,也提供了一种机制来提高安全性,那就是条件

条件的定义和处理可以用来定义在处理过程中遇到问题时相应的处理步骤,定义条件和处理程序是事先定义程序执行过程中可能遇到的问题,并且可以在处理程序中定义解决这些问题的办法,可以简单理解为异常处理,这种方法需要提前预测可能出现的问题,并提出解决办法,可以增强程序健壮性,避免程序异常停止。在mysql中定义条件和处理程序通过declare关键字。

4.1. 定义条件

条件的定义就是事先定义程序执行过程中可能遇到的问题。
语法为:

declare [条件名] condition for [错误类型]

-- 条件类型:用于定义MySql的错误
-- [错误类型] 二选一,[sqlstate 'sql状态值'] 或 [mysql错误代码] 都可以代表错误,只需填写一个即可

错误条件的类型说明中,sql状态值参数和mysql错误代码参数只需填写一个即可,用于表示mysql的错误,如常见的错误1146(42s02),sql状态值是42s02,mysql错误代码参数值是1146。

示例,如定义主键重复错误error 1062 (23000): duplicate entry '60' for key 'primary'

-- 形式一:
declare primary_key_duplicate condition for sqlstate '23000';
-- 形式二:
declare primary_key_duplicate condition for 1062;

4.1. 定义处理程序

处理程序对遇到的这些间题时应采取相应的处理方式,并且保证存储过程或函数在遇到警告或者错误的的候能继续执行。

语法:

declare [错误处理方式] handler for [错误类型] [SQL语句]

-- [错误处理方式]:有三种处理方式
-- -- -- continue:表示遇到错误不处理,继续执行
-- -- -- exit:表示遇到错误马上退出
-- -- -- undo:表示遇到错误回到前一次的操作
-- [SQL语句]可以是一个语句块,在发生定义的错误时执行的代码
-- [错误类型] 二选一,[sqlstate 'sql状态值'] 或 [mysql错误代码] 都可以代表错误,只需填写一个即可,也可以填写下面的广义错误类型
-- -- -- sqlwarning 捕获警告错误
-- -- -- not found 捕获无数据错误
-- -- -- sqlexception 捕获其他类型的错误

例如,已经定义了主键重复错误error 1062 (23000): duplicate entry '60' for key 'primary'

要对该错误继续处理,有两种方法,如下所示

-- 捕获sql状态值
-- 如果遇到sql状态值为23000的,就执行continue操作,并且设置info变量为“捕捉到错误”
declare continue handler for sqlstate '23000' set @info='捕捉到错误';


-- 捕获mysql错误代码
-- 如果遇到mysql错误代码为1062的,就执行continue操作,并且设置info变量为“捕捉到错误”
declare continue handler for 1062 set @info='捕捉到错误';

-- 或者使用广义错误状态
declare continue handler for sqlexception set @info='捕捉到错误';

-- 要是不清楚错误的种类,就全加上
declare continue handler for sqlwarning, not found, sqlexception set @info='捕捉到错误';

条件和处理程序完整的使用示例:

create procedure test()
begin
	-- 定义提示信息
	declare info varchar(10) default '没有问题';
	-- 定义主键重复错误(23000)
	-- declare primary_error condition for 1062;
	declare primary_error condition for sqlstate '23000';
	-- 定义处理程序,用于处理主键重复错误(23000)
	-- declare continue handler for 1062 set info='捕获到错误';
	declare continue handler for sqlstate '23000' set info='捕获到错误';
	-- 执行插入语句,若有23000错误抛出,则改变info值
	insert into prot_product values(1, 'test');
	-- 返回info值,查看是否发生主键重复错误
	select info;
end

5. 游标

数据库开发人员编写存储过程或者函数等存储程序时,有时需要存储程序中的MySQL代码扫描select结果集中的数据,并对结果集中的每条记录进行简单处理,通过MySQL的游标机制可以解决此类问题。

游标的使用可以概括为声明游标、打开游标、从游标中提取数据以及关闭游标。
例如下列代码:

create procedure test()
begin
	declare temp_id int; -- 定义临时id用于存储游标获取的id
	declare temp_name varchar(10) ; -- 定义临时name用于存储游标获取的name
	declare flag int default 0; -- 定义循环变量
	-- 定义1329条件,该条件在读取完数据表的最后一条记录后被触发,只能在游标之前定义条件
	declare err condition for 1329;
	-- 声明游标
	declare cur cursor for select emp.id, emp.name from emp;
	-- 定义处理程序处理1329条件,只能在游标之后
	declare continue handler for 1329 set flag = 1;
	
	-- 打开游标
	open cur;
	-- 循环读取数据
	while flag = 0 do
		-- 读取游标数据
		fetch cur into temp_id, temp_name;
		select temp_id, temp_name;
	end while;
	
	-- 关闭游标
	close cur;
end

声明游标

declare [游标名] cursor for [select语句]

使用declare语句声明游标后,此时与该游标对应的select语句并没有执行,MySQL 服务器内存中并不存在与select语句对应的结果集。

打开游标

open [游标名]

使用open语句打开游标后,与该游标对应的select语句将被执行,MySQL 服务器内存中将存放与select语句对应的结果集。

从游标中提取数据

fetch [游标名] into [变量名1],[变量名2],...,[变量n]

变量名的个数必须与声明游标时使用的select语句结果集中P的字段个数保持一致。

第一次执行fetch语句时, fetch语句从结果集中提取第一条记录,再次执行fetch语句时,fetch语句从结果集中提取第二条记录,…以此类推。fetch语句每次从结果集中仅仅提取一条记录,因此fetch语句需要循环语句的配合,才能实现整个数据表的遍历。

当使用fetch语句从游标中提取最后一条记录后,再次执行fetch语句时,将产生“ERROR 1329(02000): No data to FETCH"错误信息,数据库开发人员可以针对MySQL错误代码1329,自定义错误处理程序以便结束遍历。需要注意的是:游标错误处理程序应该放在声明游标语句之后。

关闭游标

close [游标名]

关闭游标的目的在于释放游标打开时产生的结果集,节省MySQL服务器的内存空间。游标如果没有被明确地关闭,游标将在它被声明的begin…end语句块的末尾被强制关闭。

6. 流程控制

6.1. 条件控制语句

6.1.1. if…else 语句

if语句根据条件表达式的值确定执行不同的语句块
语法:

if [条件表达式1] then 
	[语句块1];
else if [条件表达式2] then 
	[语句块2];
else 
	[语句块n];
end if;

例如下面的if…else语句

create procedure test()
begin
	-- 定义三个临时变量
	set @number_1 = 1; 
	set @number_2 = 2;
	set @number_3 = 3;
	
	if @number_1 > @number3 then 
		select @number_1;
	elseif @number_2 > @number3 then 
		select @number_2;
	else
		select @number_3;
	end if;
end

-- 返回3
6.1.2. case…when 语句

case语句用于实现比if语句分支更为复杂的条件判断
语法:

case [表达式]
	when [1] then 
		[语句块1];
	when [2] then 
		[语句块2];
	else 
		[语句块n];
end case;

需要注意的是MySQL中的case语句与C语言、Java语言等高级程序设计语言不同,在高级程序设计语言中,每个case的分支需使用"break"跳出,而MySQL无需使用"break"语句。

例如下面的case语句:

create procedure test()
begin
	declare sex char(2) default '0'; 
	
	case sex
		when '0' then
			select '女' as 'sex';
		when '1' then
			select '男' as 'sex';
		else
			select 'Null' as 'sex';
	end case;
end

-- 返回'女'

6.2. 循环语句

6.2.1. 循环控制语句leave与iterate

循环标签可以帮助循环体控制循环的运行,提高循环的灵活性。leave语句用于结束循环;iterate语句用于跳出本次循环,继而进行下次循环。

语法:

-- leave标签
leave [循环标签];

-- iterate标签
iterate [循环标签];
6.2.2. while 语句

当条件表达式的值为true时,反复执行循环体,直到条件表达式的值为false。

语法:

[循环标签]: while [条件表达式] do
	[循环体];
end while [循环标签];

例如:

create procedure test()
begin
	declare count int default 0; 
	
	-- 当count小于10时进行循环
	xh: while count<10 do
		set count = count + 1; -- count自加1
		-- 当count大于5时退出循环
		if count > 5 then
			leave xh; -- 结束标签为xh的循环
		end if;
	end while xh;
	
	select count;
end

-- 返回值为6
6.2.3. repeat 语句

与while相反,当条件表达式的值为false时,反复执行循环,直到条件表达式的值为true。
语法:

[循环标签]: repeat
	[循环体];
until [条件表达式]
end repeat [循环标签];

例如:

create procedure test()
begin
	declare count int default 0; 
	
	-- 执行循环
	xh:repeat
		set count = count + 1; -- count自加1
	until count>=10 -- 直到count大于等于10,则结束循环
	end repeat xh;
	
	select count;
end

-- 返回值为10
6.2.4. loop 语句

由于loop循环语句本身没有停止循环的语句,因此loop通常使用leave语句跳出loop循环。
语法:

[循环标签]: loop
	[循环体];
	if [条件表达式] then
		leave [循环标签];
	end if;
end loop [循环标签];

例如:

create procedure test()
begin
	declare count int default 0; 

	xh: loop
		set count = count + 1;
		if count >= 10 then
			leave xh;
		end if;
	end loop xh;
	
	select count;
end

-- 返回 10

存储过程和函数的博客分为三章,链接分别为:

  1. SQL 存储过程 procedure 讲解+代码实例
  2. SQL 函数 function 讲解+代码实例
  3. SQL 存储过程和函数的对比、变量、条件和处理程序、游标、流程控制详解+代码示例
原创文章 231 获赞 1114 访问量 147万+

猜你喜欢

转载自blog.csdn.net/baishuiniyaonulia/article/details/105615183