《MySQL必知必会》第24章 使用游标 中的bug:最后一行被重复INSERT

    在看《MySQL必知必会》第24章 使用游标的时候,手打存储过程processorders()的时候,发现ordertotals总是有重复的一行:

 《MySQL必知必会》第24章 使用游标 中的bug:最后一行被重复INSERT_第1张图片

经仔细校对原书的代码,没有发现问题;上网看别人的博客,读书笔记,暂时没有发现有人提到这个问题,还有的人代码跟书里一样,运行结果也跟书里一样(不过搞笑的是,我把他的代码复制下来,发现他代码中第一个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;

结果如下:

《MySQL必知必会》第24章 使用游标 中的bug:最后一行被重复INSERT_第2张图片

可以说明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;
代码复制到编辑区时格式错乱,调整好后正常浏览时又错乱,所以没办法,将就着看吧,见谅。

你可能感兴趣的:(MySQL)