《MySQL必知必会》第24章 有bug

第二十四~第二十六章

第二十四章

有时候,需要在检索出来的行中前进或后退一行或多行,这就是游标,游标是一个存储在MySQL服务器上的数据库查询,不是一条SELECT语句,而是被该语句检索出来的结果集

游标主要用于交互式应用,用户可以滚动屏幕上的数据进行浏览或更改

MySQL游标只能用于存储过程(和函数)

使用步骤为

1、声明(定义)游标

2、打开游标,这个过程就是把数据实际检索出来

3、根据需要取出检索出来的各行

4、关闭游标

创建、打开和和关闭游标

CREATE PROCEDURE processorders()
BEGIN
	-- 定义游标
	DECLARE ordernumbers CURSOR
	FOR
	SELECT order_num FROM orders;
	
	-- 打开游标
	OPEN ordernumbers;
	
	-- 关闭游标
	CLOSE ordernumbers;
	
END;

要注意OPEN xxx,CLOSE xxx语句应当在存储过程内(BEGIN …END之间)使用,单独使用会报错

使用FETCH分别访问每一行,会向前移动游标中的内部行指针,使下一条FETCH语句检索下一行,保证不重复读取

CREATE PROCEDURE processorders()
BEGIN
	--定义一个局部变量
	DECLARE o INT;
	
	DECLARE ordernumbers CURSOR
	FOR
	SELECT order_num FROM orders;
	
	OPEN ordernumbers;
	
	--检索第一行
	FETCH ordernumbers INTO o;
	
	CLOSE ordernumbers;

END;

将检索出的第一行保存到局部变量o中,但不做处理

循环检索数据,从第一行到最后一行

CREATE PROCEDURE processorders()
BEGIN

	--定义局部变量,必须在定义游标之前定义
	DECLARE done BOOLEAN DEFAULT 0;
	DECLARE o INT;
	
	DECLARE ordernumbers CURSOR
	FOR
	SELECT order_num FROM orders;
	
	DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
	
	OPEN ordernumbers;
	
	--循环每一行
	REPEAT
		FETCH ordernumbers INTO o;
	UNTIL done END REPEAT;
	
	CLOSE ordernumbers;
	
END;

本例中的FETCH反复执行直到done为真,所以一开始用DEFAULT 0定义done,至于done何时为真

DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;

该语句定义了一个CONTINUE HANDLER,当SQLSTATE '02000’出现时,SET done=1,SQLSTATE '02000’是一个未找到条件,当REPEAT由于没有更多的行供循环而不能继续时出现该条件

对取出的数据进行处理

-- 上一章学习过的存储过程,待会要用到
CREATE PROCEDURE ordertotal(
	IN onumber INT,
	IN taxable BOOLEAN,
	OUT ototal DECIMAL(8,2)
)	COMMENT 'Obtain order total, optionally adding tax'
BEGIN

	DECLARE total DECIMAL(8,2);
	DECLARE taxrate INT DEFAULT 6;
	
	SELECT Sum(item_price*quantity)
	FROM orderitems
	WHERE order_num = onumber
	INTO total;
	
	IF taxable THEN
		SELECT total+(total/100*taxrate) INTO total;
	END IF;
	
	SELECT total INTO ototal;
	
END;
-- 如果已经存在该过程,先删除
DROP PROCEDURE IF EXISTS processorders;

CREATE PROCEDURE processorders()
BEGIN
    -- Declare local variables
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o INT;
    DECLARE t DECIMAL(8, 2);
    
    -- Declare the cursor
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    
    -- Declare continue handler
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
    
    -- 如果表已经存在,先删除
	DROP TABLE IF EXISTS ordertotals;
    -- 创建表
    CREATE TABLE IF NOT EXISTS ordertotals (order_num INT, total DECIMAL(8, 2));
    
    -- Open the cursor
    OPEN ordernumbers;
    
    -- Loop through all rows
    REPEAT
    
        -- 取第一行
        FETCH ordernumbers INTO o;
        
        -- done仍为0
		IF NOT done THEN
        	-- 调用存储过程,将结果赋值给t 
			CALL ordertotal(o, 1, t);
        	-- 插入一行到新建的表中
			INSERT INTO ordertotals(order_num, total)
						VALUES(o, t);
				
		END IF;
 
    -- End of loop
    UNTIL done END REPEAT;
    
    -- Close the cursor  
    CLOSE ordernumbers;
END;

-- CREATE只相当于声明一个过程,CALL是调用,若没有该语句而执行接下来的SELECT语句,报错:表不存在
CALL processorders;

-- 查看该表
SELECT *
FROM ordertotals;

我们增加变量t,用于存储每个订单的带税的合计,ordertotals表存储产生的结果

这里的代码与书本中的代码不同,书中的代码有bug,导致结果的最后一行输出两次,先看有bug的代码

REPEAT
	FETCH ordernumbers INTO o;
	
	CALL ordertotal(o, 1, t);
	
	INSERT INTO ordertotals(order_num, total)
	VALUES(o, t);
	
UNTIL done END REPEAT;

大概原因如下,在检索完最后一行后,回到循环的最开始,MySQL尝试FETCH新的一行,但此时已经没有多的行可以检索,此时done变为1,理论上此时应报错结束循环,但MySQL并不如此,而是继续进行此次循环,而o,t的值为上一轮循环的值,从而导致表中最后会有重复的两行,由于此次循环后done为1,因此退出循环,这也就是为什么自己的代码的循环体中要多一次判断,当done为1时就不再执行接下来的操作

具体解释以及补充

发布了84 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43569916/article/details/104541183