一次使用存储过程游标遇到的坑
有这样一个需求:统计某省某市某区前6个月的数据,直接sql查询效率很低,于是打算做定时任务,用定时器执行存储过程的方式在每月初统计上月的相关数据。
使用存储过程就要用到游标了,之前很少写存储过程,对游标也不是熟悉,咋办呢,现学现用啦。
创建存储过程
1 CREATE 2 [DEFINER = { user | CURRENT_USER }] 3 PROCEDURE sp_name ([proc_parameter[,...]]) 4 [characteristic ...] routine_body 5 6 proc_parameter: 7 [ IN | OUT | INOUT ] param_name type 8 9 characteristic: 10 COMMENT 'string' 11 | LANGUAGE SQL 12 | [NOT] DETERMINISTIC 13 | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } 14 | SQL SECURITY { DEFINER | INVOKER } 15 16 routine_body: 17 Valid SQL routine statement 18 19 [begin_label:] BEGIN 20 [statement_list] 21 …… 22 END [end_label]
MYSQL 存储过程中的关键语法
声明语句结束符,可以自定义:
1 DELIMITER $$ 2 或 3 DELIMITER //
声明存储过程:
1 CREATE PROCEDURE demo_in_parameter(IN p_in int)
存储过程开始和结束符号:
1 BEGIN .... END
变量赋值:
1 SET @p_in=1
变量定义:
1 DECLARE l_int int unsigned default 0;
创建mysql存储过程、存储函数:
1 create procedure 存储过程名(参数)
存储过程体:
1 create function 存储函数名(参数)
游标
简介
游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。
游标充当指针的作用。
尽管游标能遍历结果中的所有行,但他一次只指向一行。
游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行相应的操作。
用法
一、声明一个游标: declare 游标名称 CURSOR for table;(这里的table可以是查询出来的任意集合)
二、打开定义的游标:open 游标名称;
三、获得下一行数据:FETCH 游标名称 into testrangeid,versionid(和查出的字段顺序保持一致);
四、需要执行的语句(增删改查):这里视具体情况而定
五、释放游标:CLOSE 游标名称;
注:mysql存储过程每一句后面必须用;结尾,使用的临时字段需要在定义游标之前进行声明。
先写之前出现的错误写法:
这样写出的双重循环,里面的会执行6次,外层只会执行一次,如果在后面把done置为0就会出现死循环。
1 CREATE DEFINER=`root`@`%` PROCEDURE `p_month_count_init`() 2 BEGIN 3 4 -- 定义变量 5 DECLARE done int DEFAULT 0; 6 declare i int DEFAULT 0; 7 declare v_yearMonth varchar(6); 8 declare v_provinceCode varchar(50) default '';-- 省 9 declare v_cityCode varchar(50) default '';-- 市 10 11 -- 定义游标,并将sql结果集赋值到游标中 12 DECLARE cur CURSOR FOR 13 select province_code,city_code from dt_lift_info where is_delete = 0 group by province_code,city_code; 14 -- 声明当游标遍历完后将标志变量置成某个值 15 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1; 16 -- 打开游标 17 open cur; 18 19 fetch cur into v_provinceCode,v_cityCode; 20 -- 当done不等于1,也就是未遍历完时,会一直循环 21 while done<>1 do 22 while i<6 DO -- 循环开始 23 set i=i+1; 24 set v_yearMonth = DATE_FORMAT( DATE_SUB(CURDATE(), INTERVAL i MONTH), '%Y%m'); 25 call p_month_count(v_yearMonth,v_provinceCode,v_cityCode); 26 end while; -- 循环结束 27 -- 将游标中的值再赋值给变量,供下次循环使用 28 fetch cur into v_provinceCode,v_cityCode; 29 -- 当s等于1时表明遍历以完成,退出循环 30 end while; 31 -- 关闭游标 32 close cur; 33 END
最后改成这样写就可以了。
1 CREATE DEFINER=`root`@`localhost` PROCEDURE `p_month_count_init`() 2 BEGIN 3 -- 定义变量 4 DECLARE done int DEFAULT 0; 5 declare i int DEFAULT 0; 6 declare v_yearMonth varchar(6); 7 declare v_provinceCode varchar(50) default '';-- 省 8 declare v_cityCode varchar(50) default '';-- 市 9 declare v_areaCode varchar(50) default '';-- 区 10 11 -- 定义游标,并将sql结果集赋值到游标中 12 DECLARE cur CURSOR FOR 13 select province_code,city_code,area_code from dt_lift_info where is_delete = 0 group by province_code,city_code,area_code; 14 -- 声明当游标遍历完后将标志变量置成某个值 15 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1; 16 17 first_loop:LOOP 18 IF i >= 6 THEN 19 LEAVE first_loop; 20 END IF; 21 set i=i+1; 22 set v_yearMonth = DATE_FORMAT( DATE_SUB(CURDATE(), INTERVAL i MONTH), '%Y%m'); 23 -- 打开游标 24 open cur; 25 second_loop:LOOP 26 fetch cur into v_provinceCode,v_cityCode,v_areaCode; 27 IF done = 1 THEN 28 LEAVE second_loop; 29 END IF; 30 call p_month_count(v_yearMonth,v_provinceCode,v_cityCode,v_areaCode); 31 end LOOP second_loop; 32 SET done = 0; -- 注意这个别漏了 33 -- 关闭游标 34 close cur; 35 END LOOP first_loop; 36 END
做完这个存储过程之后,瞬间感觉对存储过程理解的深了一点。