MySQL存储过程例子,包含事务,参数,嵌套调用,游标,循环等

数据库操作与存储过程实现
本文详细介绍了数据库操作,包括创建、删除、修改存储过程,以及如何使用存储过程进行复杂数据处理,包括增加、更新、删除操作。文章还讨论了存储过程参数类型(in、out、inout)的应用,以及如何利用存储过程处理特定编号范围内的数据。

drop procedure if exists pro_rep_shadow_rs;
delimiter |
----------------------------------
-- rep_shadow_rs
-- 用来处理信息的增加,更新和删除
-- 每次只更新上次以来没有做过的数据
-- 根据不同的标志位
-- 需要一个输出的参数,
-- 如果返回为0,则调用失败,事务回滚
-- 如果返回为1,调用成功,事务提交
--
-- 测试方法
-- call pro_rep_shadow_rs(@rtn);
-- select @rtn;
----------------------------------
create procedure pro_rep_shadow_rs(out rtn int)
begin
-- 声明变量,所有的声明必须在非声明的语句前面
declare iLast_rep_sync_id int default -1;
declare iMax_rep_sync_id int default -1;
-- 如果出现异常,或自动处理并rollback,但不再通知调用方了
-- 如果希望应用获得异常,需要将下面这一句,以及启动事务和提交事务的语句全部去掉
declare exit handler for sqlexception rollback;
-- 查找上一次的
select eid into iLast_rep_sync_id from rep_de_proc_log where tbl='rep_shadow_rs';
-- 如果不存在,则增加一行
if iLast_rep_sync_id=-1 then
insert into rep_de_proc_log(rid,eid,tbl) values(0,0,'rep_shadow_rs');
set iLast_rep_sync_id = 0;
end if;

-- 下一个数字
set iLast_rep_sync_id=iLast_rep_sync_id+1;
-- 设置默认的返回值为0:失败
set rtn=0;

-- 启动事务
start transaction;
-- 查找最大编号
select max(rep_sync_id) into iMax_rep_sync_id from rep_shadow_rs;
-- 有新数据
if iMax_rep_sync_id>=iLast_rep_sync_id then
-- 调用
call pro_rep_shadow_rs_do(iLast_rep_sync_id,iMax_rep_sync_id);
-- 更新日志
update rep_de_proc_log set rid=iLast_rep_sync_id,eid=iMax_rep_sync_id where tbl='rep_shadow_rs';
end if;

-- 运行没有异常,提交事务
commit;
-- 设置返回值为1
set rtn=1;
end;
|
delimiter ;
drop procedure if exists pro_rep_shadow_rs_do;
delimiter |
---------------------------------
-- 处理指定编号范围内的数据
-- 需要输入2个参数
-- last_rep_sync_id 是编号的最小值
-- max_rep_sync_id 是编号的最大值
-- 无返回值
---------------------------------
create procedure pro_rep_shadow_rs_do(last_rep_sync_id int, max_rep_sync_id int)
begin
declare iRep_operationtype varchar(1);
declare iRep_status varchar(1);
declare iRep_Sync_id int;
declare iId int;
-- 这个用于处理游标到达最后一行的情况
declare stop int default 0;
-- 声明游标
declare cur cursor for select id,Rep_operationtype,iRep_status,rep_sync_id from rep_shadow_rs where rep_sync_id between last_rep_sync_id and max_rep_sync_id;
-- 声明游标的异常处理,设置一个终止标记
declare CONTINUE HANDLER FOR SQLSTATE '02000' SET stop=1;

-- 打开游标
open cur;

-- 读取一行数据到变量
fetch cur into iId,iRep_operationtype,iRep_status,iRep_Sync_id;
-- 这个就是判断是否游标已经到达了最后
while stop <> 1 do
-- 各种判断
if iRep_operationtype='I' then
insert into rs0811 (id,fnbm) select id,fnbm from rep_shadow_rs where rep_sync_id=iRep_sync_id;
elseif iRep_operationtype='U' then
begin
if iRep_status='A' then
insert into rs0811 (id,fnbm) select id,fnbm from rep_shadow_rs where rep_sync_id=iRep_sync_id;
elseif iRep_status='B' then
delete from rs0811 where id=iId;
end if;
end;
elseif iRep_operationtype='D' then
delete from rs0811 where id=iId;
end if;

-- 读取下一行的数据
fetch cur into iId,iRep_operationtype,iRep_status,iRep_Sync_id;
end while; -- 循环结束
close cur; -- 关闭游标
end;

use testprocedure;

delimiter //


create procedure simpleproce1 (out par1 int)
begin
select count(*) into par1 from proce;
end
//

delimiter ;
call simpleproce1(@a);

select @a;

#<2>,每次只有单一的行可以被取回select id,name into par1,par2 from proce LIMIT 1;中的LIMIT 1;

use testprocedure;

delimiter //

DROP procedure IF EXISTS simpleproce2

create procedure simpleproce2 (out par1 int,out par2 char(30))
begin
select id,name into par1,par2 from proce LIMIT 1;
end
//

delimiter ;
call simpleproce2(@a,@b);

select @a,@b;


## *********second test,function************
#<3>
delimiter //

DROP FUNCTION IF EXISTS hello
//

create function hello(s char(20)) returns char(50)
return concat('Hello, ',s,'!');
//

delimiter ;
select hello('world');

show create function testprocedure.helloG

#它返回子程序的特征,如数据库,名字,类型,创建者及创建和修改日期
show function status like 'hello'G

#<4>
#注意name不能和字段名相同
delimiter //
DROP procedure IF EXISTS test //

CREATE PROCEDURE test ()
BEGIN
DECLARE name VARCHAR(5) DEFAULT 'bob';
DECLARE newname VARCHAR(5);
DECLARE xid INT;

SELECT name,id INTO newname,xid
FROM proce WHERE name = name;
SELECT newname;
END;
//

call test1() //

#***
delimiter //
DROP procedure IF EXISTS test2 //

CREATE PROCEDURE test2 ()
BEGIN

DECLARE newname VARCHAR(5);
DECLARE xid INT;

SELECT name,id INTO newname,xid
FROM proce limit 1;
SELECT newname,xid;
END;
//

call test2() //

#<5>
use testprocedure;
CREATE PROCEDURE p1 () SELECT * FROM proce;

call p1();

#<6>注意此处的handler是设置SQLSTATE值,SQLWARNING是对所有以01开头的SQLSTATE代码的速记
#NOT FOUND是对所有以02开头的SQLSTATE代码的速记
#SQLEXCEPTION是对所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE代码的速记
#DECLARE CONTINUE HANDLER声明CONTINUE异常处理
#事实上这里的23000SQLSTATE是更常用的,当外键约束出错或主键约束出错就被调用了。
#当没有发生该23000异常时, select @x2的值将是null,而不是1,
#并且后面的第2个语句执行时将会报主键约束错误,此时@x2=1,@x=4,虽然第2句有了异常,但是后面的语句继续执行
#保存到数据的数据是3,test3和5,test5

use testprocedure;
delimiter //
DROP procedure IF EXISTS handlerdemo
//

create procedure handlerdemo()
begin
declare continue handler for sqlstate '23000' set @x2=1;
set @x=1;
insert into proce values(3,'test3');
set @x=2;
insert into proce values(3,'test4');
set @x=3;
insert into proce values(5,'test5');
set @x=4;
end;
//

call handlerdemo()//

select @x //
select @x2 //

## ************光标****************
#<7>光标必须在声明处理程序之前被声明,并且变量和条件必须在声明光标或处理程序之前被声明
#在这里先声明变量a,b,c,后声明cursor
create procedure curdemo()
begin
declare done int default 0;
declare a char(16);
declare b,c int;
declare cur1 cursor for select id,name from proce;
declare cur2 cursor for select id from proce2;
declare continue handler for sqlstate '02000' set done=1;

open cur1;
open cur2;

repeat
fetch cur1 into b,a;
fetch cur2 into c;
if not done then
if b<c then
insert into proce3 values(b,a);
else
insert into proce3 values(c,a);
end if;
end if;
until done end repeat;

close cur1;
close cur2;
end

## **************** Case *******************
#<8>when ... then ;case ... end case;
delimiter //
DROP procedure IF EXISTS p13
//
create procedure p13(in par1 int)
begin
declare var1 int;
set var1=par1+1;

case var1
when 0 then insert into casetest values(17);
when 1 then insert into casetest values(18);
else insert into casetest values(19);
end case;
end;
//

call p13(-1)//
call p13(0)//
call p13(1)//
call p13(null)//


## **************** while ****************
#<9>while ... do ... end while;为了防止null的错误,set v=0是必须的
delimiter //
DROP procedure IF EXISTS p14
//

create procedure p14()
begin
declare v int;
set v=0;
while v < 5 do
insert into casetest values (v);
set v=v+1;
end while;
end;//

call p14()//

## ***************** repeat *****************
#<10>repeat ...until ... end repeat; 是执行后检查(until v>=5),而while是执行前检查(while v<5)
delimiter //
DROP procedure IF EXISTS p15
//

create procedure p15()
begin
declare v int;
set v=0;
repeat
insert into casetest values(v);
set v=v+1;
until v >=5

end repeat;

end;
//

call p15()//

## ***************** loops *****************
#<11> loop 和while一样不需要初始条件,同时和repeat一样不需要结束条件
# loop_label: loop
# ...
# if .. then
# leave loop_label
# end if
# end loop

delimiter //
DROP procedure IF EXISTS p16
//

create procedure p16()
begin
declare v int;
set v=0;
loop_label: loop
insert into casetest values(v);
set v=v+1;
if v >=5 then
leave loop_label;
end if;
end loop;
end;//

call p16()//

## ***************** Labels *****************
# <12>labels标号; 注意此处的until 0=0后面没有分号“;”
delimiter //
DROP procedure IF EXISTS p17//

create procedure p17()
label_1:begin

label_2:while 0=1 do leave label_2; end while;

label_3:repeat leave label_3;until 0=0 end repeat;

label_4:loop leave label_4; end loop;

end;//

call p17()//

#<13>labels 标号结束符;
delimiter //
DROP procedure IF EXISTS p18//

create procedure p18()
label_1:begin

label_2:while 0=1 do leave label_2; end while label_2;

label_3:repeat leave label_3;until 0=0 end repeat label_3;

label_4:loop leave label_4; end loop label_4;

end label_1;//

call p18()//

#<14>leave和labels 跳出和标号;leave 使程序跳出复杂的语句
delimiter //
DROP procedure IF EXISTS p19//

create procedure p19(par char)

label_1:begin
label_2:begin
label_3:begin

if par is not null then
if par='a' then leave label_1;
else
begin
if par='b' then
leave label_2;
else
leave label_3;
end if;
end;
end if;
end if;

end label_3;
end label_2;
end label_1;

//

call p19('a')//


#<15>iterate迭代,必须用leave;iterate意思是重新开始复合语句,相当于 continue
#该结果中3将不被保存到数据库表中
delimiter //
DROP procedure IF EXISTS p20//

create procedure p20()
begin
declare v int;
set v=0;
loop_label:loop

if v=3 then
set v=v+1;
iterate loop_label;
end if;
insert into casetest values(v);
set v=v+1;

if v>=5 then
leave loop_label;
end if;

end loop loop_label;
end;//

call p20()//


#<16>Grand combination大组合

delimiter //
DROP procedure IF EXISTS p21//

create procedure p21(in par1 int,out par2 int)
language sql deterministic sql security invoker
begin
declare v int;

label goto_label;

start_label:loop
if v=v then
leave start_label;
else
iterate start_label;
end if;
end loop start_label;

repeat
while 1=0 do begin end;
end while;
until v=v
end repeat;

goto goto_label;


end;
//

call p21()//

## **************** trigger ***************************
#<17>
use testprocedure;

CREATE TABLE trig1(a1 int);
CREATE TABLE trig2(a2 int);
CREATE TABLE trig3(a3 int not null AUTO_INCREMENT PRIMARY KEY);

CREATE TABLE trig4(
a4 INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
b4 INT DEFAULT 0
);

insert into trig3(a3) values(null),(null),(null),(null),(null),(null),(null),(null),(null),(null);
insert into trig4(a4) values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0);

delimiter //
DROP trigger trigtest//

create trigger trigtest before insert on trig1
for each row begin
insert into trig2 set a2=NEW.a1;
delete from trig3 where a3=NEW.a1;
update trig4 set b4=b4+1 where a4=NEW.a1;
end;
//


delimiter ;

INSERT INTO trig1 VALUES(1), (3), (1), (7), (1), (8), (4), (4);

------ -------------------------------------------------------------------------

Drop Procedure If Exists p_report;

Delimiter forend

Create Procedure p_report
(In year Int,
In month Int,
In Id Char(10),
Out status Int)
Begin
SelectYear,
Month,
OfficeId,
OnTimeRate
FromReportByMonth
WhereOfficeId = Id And
Year = year;

End

forend

Delimiter ;

--------------------------------------------------------------------------------

MySQL 存储过程参数有三种类型:in、out、inout。它们各有什么作用和特点呢?

一、MySQL 存储过程参数(in)

MySQL 存储过程 “in” 参数:跟 C 语言的函数参数的值传递类似, MySQL 存储过程内部可能会修改此参数,但对 in 类型参数的修改,对调用者(caller)来说是不可见的(not visible)。

drop procedure if exists pr_param_in;

create procedure pr_param_in
(
   in id int -- in 类型的 MySQL 存储过程参数
)
begin
   if (id is not null) then
      set id = id + 1;
   end if;

   select id as id_inner;
end;
set @id = 10;

call pr_param_in(@id);

select @id as id_out;
mysql> call pr_param_in(@id);
+----------+
| id_inner |
+----------+
|       11 |
+----------+

mysql> select @id as id_out;
+--------+
| id_out |
+--------+
| 10     |
+--------+

可以看到:用户变量 @id 传入值为 10,执行存储过程后,在过程内部值为:11(id_inner),但外部变量值依旧为:10(id_out)。

二、MySQL 存储过程参数(out)

MySQL 存储过程 “out” 参数:从存储过程内部传值给调用者。在存储过程内部,该参数初始值为 null,无论调用者是否给存储过程参数设置值。

drop procedure if exists pr_param_out;

create procedure pr_param_out
(
   out id int
)
begin
   select id as id_inner_1;  -- id 初始值为 null

   if (id is not null) then
      set id = id + 1;

      select id as id_inner_2;
   else
      select 1 into id;
   end if;

   select id as id_inner_3;
end;
set @id = 10;

call pr_param_out(@id);

select @id as id_out;
mysql> set @id = 10;
mysql>
mysql> call pr_param_out(@id);
+------------+
| id_inner_1 |
+------------+
|       NULL |
+------------+

+------------+
| id_inner_3 |
+------------+
|          1 |
+------------+

mysql> select @id as id_out;
+--------+
| id_out |
+--------+
| 1      |
+--------+

可以看出,虽然我们设置了用户定义变量 @id 为 10,传递 @id 给存储过程后,在存储过程内部,id 的初始值总是 null(id_inner_1)。最后 id 值(id_out = 1)传回给调用者。

三、MySQL 存储过程参数(inout)

MySQL 存储过程 inout 参数跟 out 类似,都可以从存储过程内部传值给调用者。不同的是:调用者还可以通过 inout 参数传递值给存储过程。

drop procedure if exists pr_param_inout;

create procedure pr_param_inout
(
   inout id int
)
begin
   select id as id_inner_1;  -- id 值为调用者传进来的值

   if (id is not null) then
      set id = id + 1;

      select id as id_inner_2;
   else
      select 1 into id;
   end if;

   select id as id_inner_3;
end;
set @id = 10;

call pr_param_inout(@id);

select @id as id_out;
mysql> set @id = 10;

mysql>
mysql> call pr_param_inout(@id);
+------------+
| id_inner_1 |
+------------+
|         10 |
+------------+

+------------+
| id_inner_2 |
+------------+
|         11 |
+------------+

+------------+
| id_inner_3 |
+------------+
|         11 |
+------------+
mysql>
mysql> select @id as id_out;
+--------+
| id_out |
+--------+
| 11     |
+--------+

从结果可以看出:我们把 @id(10),传给存储过程后,存储过程最后又把计算结果值 11(id_inner_3)传回给调用者。 MySQL 存储过程 inout 参数的行为跟 C 语言函数中的引用传值类似。

通过以上例子:如果仅仅想把数据传给 MySQL 存储过程,那就使用“in” 类型参数;如果仅仅从 MySQL 存储过程返回值,那就使用“out” 类型参数;如果需要把数据传给 MySQL 存储过程,还要经过一些计算后再传回给我们,此时,要使用“inout” 类型参数。

<think>我们正在讨论MySQL游标的使用,特别是如何实现类似for循环的遍历。根据提供的引用信息,MySQL游标允许我们在查询结果集上逐行执行操作。虽然MySQL本身没有直接的FOR循环语法来处理游标,但我们可以使用LOOP、REPEAT或WHILE循环配合游标来实现类似功能。 步骤: 1. 声明游标:定义一个游标来保存查询结果。 2. 打开游标:执行查询并将结果集放入游标。 3. 循环获取数据:使用循环语句(如LOOP)和游标的FETCH语句来逐行读取数据。 4. 处理数据:对每一行数据进行处理。 5. 关闭游标:完成操作后关闭游标。 注意:使用游标前需要声明变量来存储每行提取的数据,同时还要声明一个NOT FOUND的处理程序以避免循环中的错误。 下面是一个示例存储过程,它演示了如何使用游标进行循环遍历: ```sql DELIMITER $$ CREATE PROCEDURE example_cursor_loop() BEGIN -- 声明变量用于存储每行的数据 DECLARE done INT DEFAULT FALSE; DECLARE employee_id INT; DECLARE employee_name VARCHAR(255); -- 声明游标 DECLARE cur CURSOR FOR SELECT id, name FROM employees; -- 声明当游标没有更多行时设置done为TRUE DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- 打开游标 OPEN cur; -- 开始循环 read_loop: LOOP FETCH cur INTO employee_id, employee_name; IF done THEN LEAVE read_loop; END IF; -- 在这里处理每一行数据,例如:打印或插入到其他表 -- 这里简单演示:调用一个打印过程(假设存在)或进行其他操作 -- 比如:INSERT INTO some_table (col1, col2) VALUES (employee_id, employee_name); END LOOP; -- 关闭游标 CLOSE cur; END$$ DELIMITER ; ``` 在这个例子中: - 我们首先声明了几个变量:`done`用于标记循环结束,`employee_id`和`employee_name`用于存储游标中提取的每一行的值。 - 声明游标`cur`,它从一个名为`employees`的表中选择`id`和`name`。 - 声明了一个CONTINUE HANDLER,当游标没有更多行可提取时(即NOT FOUND),将`done`设置为TRUE。 - 打开游标后,进入名为`read_loop`的循环。 - 在循环中,首先使用FETCH语句将当前行的数据提取到变量中。 - 然后检查`done`变量,如果为TRUE则跳出循环(使用LEAVE语句)。 - 否则,在循环体内处理这一行数据(这里只是一个示例,你可以替换为实际的操作)。 - 循环结束后关闭游标。 需要注意的是,游标在处理大量数据时可能效率较低,因为它是一行一行处理的。如果可能,尽量使用集合操作(如一条SQL语句更新多行)来代替游标循环。 另外,引用[3]提到在存储过程中实现双层循环嵌套游标)也是可能的,但同样需要注意性能问题。嵌套游标的基本思路是:在外层游标循环中打开内层游标,然后内层游标再遍历自己的结果集。 例如,双层游标循环的伪代码结构: ```sql DECLARE outer_done INT DEFAULT FALSE; DECLARE outer_id INT; DECLARE outer_cur CURSOR FOR ...; DECLARE CONTINUE HANDLER FOR NOT FOUND SET outer_done = TRUE; OPEN outer_cur; outer_loop: LOOP FETCH outer_cur INTO outer_id; IF outer_done THEN LEAVE outer_loop; END IF; -- 内部游标开始 BEGIN DECLARE inner_done INT DEFAULT FALSE; DECLARE inner_value INT; DECLARE inner_cur CURSOR FOR ...; -- 内部查询可能依赖outer_id DECLARE CONTINUE HANDLER FOR NOT FOUND SET inner_done = TRUE; OPEN inner_cur; inner_loop: LOOP FETCH inner_cur INTO inner_value; IF inner_done THEN LEAVE inner_loop; END IF; -- 处理内部游标的每一行 END LOOP; CLOSE inner_cur; END; END LOOP; CLOSE outer_cur; ``` 请根据实际需求调整。注意,为了避免变量名冲突,内部游标的逻辑放在了一个BEGIN...END块中,并声明了自己的处理程序和变量。 希望这个回答对你有所帮助。 §§相关问题§§ 1. 在MySQL存储过程中如何避免游标使用中的性能问题? 2. 除了游标MySQL还有哪些处理结果集的方法? 3. 如何在MySQL中实现嵌套游标循环(双层循环)? 4. 游标与临时表结合使用的场景有哪些? </think>以下是MySQL中使用游标实现FOR循环的详细方法,结合了引用中的关键信息: ### MySQL游标FOR循环使用步骤 1. **声明变量和游标** 需先定义存储数据的变量和游标对象: ```sql DECLARE done BOOLEAN DEFAULT FALSE; DECLARE var_id INT; DECLARE var_name VARCHAR(50); DECLARE cur CURSOR FOR SELECT id, name FROM your_table; -- 定义查询结果集[^2] ``` 2. **设置终止处理器** 声明当游标无数据时的处理逻辑: ```sql DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; ``` 3. **打开游标启动循环** 使用`OPEN`激活游标,通过`LOOP`实现循环: ```sql OPEN cur; read_loop: LOOP FETCH cur INTO var_id, var_name; -- 逐行获取数据 IF done THEN LEAVE read_loop; END IF; -- 此处添加业务逻辑(示例) INSERT INTO result_table VALUES(var_id, var_name); END LOOP; CLOSE cur; -- 必须显式关闭游标[^1] ``` ### 关键特性说明 1. **单向遍历** 游标仅支持向前遍历(不可滚动),每次`FETCH`获取下一行[^1]。 2. **只读性** 不能通过游标直接修改数据(如`UPDATE`/`DELETE`)[^1]。 3. **性能优化** - 敏感游标(默认)直接访问物理数据,速度快于不敏感游标[^1]。 - 避免在循环内执行复杂查询,可减少性能损耗[^3]。 ### 完整存储过程示例 ```sql DELIMITER $$ CREATE PROCEDURE process_data() BEGIN DECLARE done BOOLEAN DEFAULT FALSE; DECLARE v_id INT; DECLARE cur CURSOR FOR SELECT id FROM main_table; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; LOOP FETCH cur INTO v_id; IF done THEN LEAVE; END IF; -- 嵌套循环示例(双层游标)[^3] INSERT INTO log_table VALUES(v_id, NOW()); END LOOP; CLOSE cur; END$$ DELIMITER ; ``` > **注意**:游标处理大量数据时效率较低,建议在无法用单一SQL实现复杂逻辑时才使用[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值