在看《MySQL必知必会》第24章 使用游标的时候,手打存储过程processorders()的时候,发现ordertotals总是有重复的一行:
经仔细校对原书的代码,没有发现问题;上网看别人的博客,读书笔记,暂时没有发现有人提到这个问题,还有的人代码跟书里一样,运行结果也跟书里一样(不过搞笑的是,我把他的代码复制下来,发现他代码中第一个DECLARE还打错了),居然这么玄学?
对书中代码一番仔细端详后,感觉FETCH跟java中的Iterator的next()很像,next()没有元素可以访问的时候直接抛NoSuchElementException异常,当FETCH没有元素可以访问时会发生什么呢?于是我把每获取完一行数据后把done的状态也INSERT到ordertotals表中,修改后代码如下:
CREATE TABLE IF NOT EXISTS ordertotals(order_num INT,
total DECIMAL(8, 2),
done BOOLEAN);
#清空数据
DELETE FROM ordertotals;
OPEN ordernumbers;
#具有bug的代码, 最后一行被INSERT两次
REPEAT
FETCH ordernumbers INTO o; #从游标中fetch数据到局部变量
CALL ordertotal(o, 1, t);
INSERT INTO ordertotals(order_num, total, done)
VALUES(o, t, done);
UNTIL done END REPEAT;
结果如下:
可以说明FETCH不到数据时,MySQL不会报错,也不会改变要填充的变量o的值,因此在使用变量o之前必须检验done的状态,改进代码如下:
#正确的repeat代码, 需要两次判断, 略繁琐
REPEAT
FETCH ordernumbers INTO o; #从游标中fetch数据到局部变量
IF NOT done THEN
CALL ordertotal(o, 1, t);
INSERT INTO ordertotals(order_num, total, done)
VALUES(o, t, done);
END IF;
UNTIL done END REPEAT;
输出结果:
在这种情况下,其实使用WHILE或LOOP更加简洁,完整代码如下:
#先删除原来的存储过程
DROP PROCEDURE IF EXISTS processorders;
DELIMITER $
CREATE PROCEDURE processorders()
BEGIN
#在定义游标,句柄之前先定义局部变量
DECLARE done BOOLEAN DEFAULT 0;
DECLARE o INT;
DECLARE t DECIMAL(8, 2);
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
#在定义游标之后定义句柄(handler)alter
#注意: 可以使用NOT FOUND来代替SQLSTATE '02000', 有助记忆
DECLARE CONTINUE HANDLER
FOR NOT FOUND
SET done = 1;
#删除表
DROP TABLE IF EXISTS ordertotals;
#创建表
CREATE TABLE IF NOT EXISTS ordertotals(order_num INT,
total DECIMAL(8, 2),
done BOOLEAN);
#清空数据
DELETE FROM ordertotals;
OPEN ordernumbers; #打开游标
/*
#具有bug的代码, 最后一行被INSERT两次
REPEAT
FETCH ordernumbers INTO o; #从游标中fetch数据到局部变量
CALL ordertotal(o, 1, t);
INSERT INTO ordertotals(order_num, total, done)
VALUES(o, t, done);
UNTIL done END REPEAT;
*/
/*
#正确的repeat代码, 需要两次判断, 略繁琐
REPEAT
FETCH ordernumbers INTO o; #从游标中fetch数据到局部变量
IF NOT done THEN
CALL ordertotal(o, 1, t);
INSERT INTO ordertotals(order_num, total, done)
VALUES(o, t, done);
END IF;
UNTIL done END REPEAT;
*/
FETCH ordernumbers INTO o;
WHILE NOT done DO #注意有个DO
CALL ordertotal(o, 1, t);
INSERT INTO ordertotals(order_num, total, done)
VALUES(o, t, done);
FETCH ordernumbers INTO o; #尝试获取值
END WHILE;
/*
fetch_loop : LOOP
FETCH ordernumbers INTO o;
IF done THEN
LEAVE fetch_loop;
END IF;
CALL ordertotal(o, 1, t);
INSERT INTO ordertotals(order_num, total, done)
VALUES(o, t, done);
END LOOP;
*/
CLOSE ordernumbers;
END;
代码复制到编辑区时格式错乱,调整好后正常浏览时又错乱,所以没办法,将就着看吧,见谅。