第二十四~第二十六章
第二十四章
有时候,需要在检索出来的行中前进或后退一行或多行,这就是游标,游标是一个存储在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时就不再执行接下来的操作