mysql存储过程游标的使用 + 使用游标时(NOT FOUND)会提前执行的坑

记录一下:捕获游标not found异常后,结束循环,遇到的坑

1.游标介绍:

游标类似于指针,但是游标可以暂存每一行的数据

游标的作用是对查询到的数据进行逐行读取的,方便对每一行的数据进行操作:

  1. 游标是只读的,也就是不能更新它;
  2. 游标是不能滚动的,也就是只能在一个方向上进行遍历,不能在记录之间随意进退,不能跳过某些记录;
  3. 避免在已经打开游标的表上更新数据。

2.游标的使用方式

首先用DECLARE语句声明一个游标

DECLARE 游标名称 CURSOR FOR 表(可以是你sql查询的任意集合);

其次需要使用OPEN语句来打开上面你定义的游标

OPEN 游标名称;

接下来你可以用FETCH语句来获得下一行数据,并且游标也将移动到对应的记录上(这个就类似java里面的那个iterator)
注意获取下一行游标时, 是将这一行数据的各个字段,按照顺序赋值到对应的变量中的,每个变量用逗号隔开

FETCH 游标名称 INTO 变量a,变量b;

然后最后当我们所需要进行的操作都结束后我们要把游标释放掉。

CLOSE 游标名称;

重点:一般使用游标时,都会涉及到遍历,那遍历结束的点是什么呢?肯定是游标将每一行数据读取完毕之后即为遍历完成。
所以这里就会涉及到我们题目所说的 NOT FOUND 异常了

因为游标遍历到最后一行后,再获取下一行会有出现 NOT FOUND 异常,这时我们事先会通过下面这条语句对这个异常进行处理:

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE

这个语句的意思就是 当捕获到not found异常之后 程序会 continue 而不是终止运行,并且设置 done 的值为true。
其中done是我们自定义(DECLARE)的一个变量,在循环中 会判断 done的值如果为true时,则停止循环。

下面是示例(其中的done变量 我用变量s代替了,0,1表示false和true,基本原理是一样的,没有影响):

CREATE DEFINER=`cosmic`@`%` PROCEDURE `demo`()
BEGIN

	DECLARE s int DEFAULT 0;
	DECLARE var_name bigint DEFAULT 0;
	DECLARE canx BIGINT DEFAULT 0;
	DECLARE canpdl BIGINT DEFAULT 0;
	DECLARE biaomjg BIGINT DEFAULT 0;
	DECLARE ziyqb VARCHAR(255) DEFAULT "";
	-- 定义游标 eachone
	DECLARE eachone CURSOR FOR 
	SELECT 
		fhbis_resource_lines,
		fhbis_product_type,
		fhbis_surface_structer,
		DATE_FORMAT(fhbis_price_period, '%Y-%m-%d') fhbis_price_period 
	FROM
		t_sm_salorder_z 
	JOIN 	t_sm_salorder t ON t_sm_salorder_z.fid = t.fid
	WHERE
		( fhbis_product_line_group IS NULL OR fhbis_product_line_group = 0 ) 
		AND fhbis_bussiness_area = 1007729491558026240 
		AND fhbis_price_period BETWEEN '2022-01-01' AND '2022-06-22' 
		AND t.fbillstatus = 'C' 
	GROUP BY
		fhbis_resource_lines,
		fhbis_product_type,
		fhbis_surface_structer,
		fhbis_price_period
	;
			
	-- 游标中的内容执行完后将s设置为1 
	DECLARE CONTINUE HANDLER FOR not found SET s=1; 
	-- 开启游标
	OPEN eachone;
	-- 开始遍历
	r_loop: LOOP
		-- 如果游标读取完毕则停止循环
		IF s = 1 THEN  
			LEAVE r_loop; 
		END IF;
		-- 获取游标的下一行值,并一次赋值给变量
		FETCH eachone INTO canx,canpdl,biaomjg,ziyqb;
		
		-- 利用游标获取的值进行其他操作
		SELECT t.fid into var_name FROM t_hbis_product_line_group t LEFT JOIN t_hbis_entryentity te ON t.fid = te.fid 
		WHERE (DATE_FORMAT(t.fhbis_datestart, '%Y-%m-%d') <= ziyqb AND DATE_FORMAT(t.fhbis_dateend, '%Y-%m-%d') >= ziyqb)
		AND te.fhbis_product_line = canx 
		AND te.fhbis_productclassify = canpdl 
		AND te.fhbis_surface_structure = biaomjg 
		AND t.fstatus = 'C'
		LIMIT 0,1;

	END LOOP r_loop;
	-- 关闭游标
	CLOSE eachone;
END

以上示例展示了游标的标准用法,这么用基本没有什么问题,但是这还没有结束!!!!!

3遇到的坑

你以为这样就万无一失了吗?? of course not!!! 这就不能不提今天的遇到的这个坑了:

因为我们捕获的是全局的 not found 异常,通常是捕获游标在遍历完最后一行后再获取下一行会抛这个异常的,但是!并不是只有游标会抛这个异常。。。

当你的程序中有其他数据或查询结果为空也会抛这个异常,会导致你本来游标还没有遍历完毕,但是 变量 done 这时变成了 true
然后你的循环就停止了,剩余的数据就没有处理。。。


在网上找了很多方式,目前看来就是一定要避免你的查询或者数据为NULL的情况
例如上面的实例:我需要对游标查询出来的数据作为条件再次查询另外一张表,但是不能保证一定可以查询到数据,所以就会有查询的数据为空的情况,这就会导致 变量s 变为 1
结合业务场景:

因为我需要通过游标中的数据再次查询另外一张表的一条数据,并且获取到这条数据的id,所以我在最开始定义了一个变量 var_name,通过每次循环将这个id赋值给var_name,下面在通过var_name进行其他的数据操作。

sql为:

SELECT t.fid into var_name FROM t_hbis_product_line_group t LEFT JOIN t_hbis_entryentity te ON t.fid = te.fid 
		WHERE (DATE_FORMAT(t.fhbis_datestart, '%Y-%m-%d') <= ziyqb AND DATE_FORMAT(t.fhbis_dateend, '%Y-%m-%d') >= ziyqb)
		AND te.fhbis_product_line = canx 
		AND te.fhbis_productclassify = canpdl 
		AND te.fhbis_surface_structure = biaomjg 
		AND t.fstatus = 'C'
		LIMIT 0,1

这里把 t.fid 使用 into 赋值给 var_name 变量,但是这并不能解决 当查询结果 t.fid 为空时 not found 异常导致的循环中断的问题

所以要修改为:

		SET var_name = (SELECT t.fid FROM t_hbis_product_line_group t LEFT JOIN t_hbis_entryentity te ON t.fid = te.fid 
		WHERE (DATE_FORMAT(t.fhbis_datestart, '%Y-%m-%d') <= ziyqb AND DATE_FORMAT(t.fhbis_dateend, '%Y-%m-%d') >= ziyqb)
		AND te.fhbis_product_line = canx 
		AND te.fhbis_productclassify = canpdl 
		AND te.fhbis_surface_structure = biaomjg 
		AND t.fstatus = 'C'
		LIMIT 0,1);
		
		IF var_name is NULL THEN
			set var_name = 0;
		END IF;

将 fid into var_name 的 赋值方式 修改为 set var_name = (查询) 的方式赋值,并且继续对 var_name 进行判空处理,保证不为null 这样就结局了 NOT FOUND 异常问题了

最后是是完整代码:

 CREATE DEFINER=`cosmic`@`%` PROCEDURE `demo`()
BEGIN

	DECLARE count int DEFAULT 0;
	DECLARE s int DEFAULT 0;
	DECLARE var_name bigint DEFAULT 0;
	DECLARE canx BIGINT DEFAULT 0;
	DECLARE canpdl BIGINT DEFAULT 0;
	DECLARE biaomjg BIGINT DEFAULT 0;
	DECLARE ziyqb VARCHAR(255) DEFAULT "";

	DECLARE eachone CURSOR FOR 
	SELECT 
		fhbis_resource_lines,
		fhbis_product_type,
		fhbis_surface_structer,
		DATE_FORMAT(fhbis_price_period, '%Y-%m-%d') fhbis_price_period 
	FROM
		t_sm_salorder_z 
	JOIN 	t_sm_salorder t ON t_sm_salorder_z.fid = t.fid
	WHERE
		( fhbis_product_line_group IS NULL OR fhbis_product_line_group = 0 ) 
		AND fhbis_bussiness_area = 1007729491558026240 
		AND fhbis_price_period BETWEEN '2022-01-01' AND '2022-06-22' 
		AND t.fbillstatus = 'C' 
	GROUP BY
		fhbis_resource_lines,
		fhbis_product_type,
		fhbis_surface_structer,
		fhbis_price_period
	;
			
	-- 游标中的内容执行完后将s设置为1 
	DECLARE CONTINUE HANDLER FOR not found SET s=1; 

	OPEN eachone;

	r_loop: LOOP

		IF s = 1 THEN  
			LEAVE r_loop; 
		END IF;
		
		FETCH eachone INTO canx,canpdl,biaomjg,ziyqb;
		
		SET count = count + 1;
		
		SET var_name = (SELECT t.fid FROM t_hbis_product_line_group t LEFT JOIN t_hbis_entryentity te ON t.fid = te.fid 
		WHERE (DATE_FORMAT(t.fhbis_datestart, '%Y-%m-%d') <= ziyqb AND DATE_FORMAT(t.fhbis_dateend, '%Y-%m-%d') >= ziyqb)
		AND te.fhbis_product_line = canx 
		AND te.fhbis_productclassify = canpdl 
		AND te.fhbis_surface_structure = biaomjg 
		AND t.fstatus = 'C'
		LIMIT 0,1);
		
		IF var_name is NULL THEN
			set var_name = 0;
		END IF;


		IF var_name <> 0 THEN
			UPDATE t_sm_salorder_z 
			JOIN t_sm_salorder T ON t_sm_salorder_z.FID = T.FID 
			SET fhbis_product_line_group = var_name
			WHERE
				( fhbis_product_line_group IS NULL OR fhbis_product_line_group = 0 ) 
				AND fhbis_bussiness_area = 1007729491558026240 
				AND fhbis_resource_lines = canx
				AND fhbis_product_type = canpdl
				AND fhbis_surface_structer = biaomjg
				AND t.fbillstatus = 'C'
				AND DATE_FORMAT(fhbis_price_period, '%Y-%m-%d') = ziyqb;
		END IF;

	END LOOP r_loop;

	CLOSE eachone;

	SELECT count;

END

以上就是根据我的业务场景做的避坑处理了。。。。小伙伴们觉得有用可以点赞支持一下,也可以将自己的心得或者更好的方式评论到下方,大家一起学习,嘻嘻嘻
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值