位于内存中的 "临时表"
。 具体如下:游标是从数据表中提取出来的数据,以 临时表 的形式存放到内存中
,在游标中有一个 数据指针, 在初始状态下指向的是首记录,利用fetch
语句可以移动该指针,从而对游标中的数据进行各种操作,然后将操作结果写回到数据库中。
1、用来查询数据库,获取记录集合(结果集)的指针,可以让开发者
一次访问一行结果集
, 在每条结果集上作操作。
2、用 ‘牺牲内存’ 来提升 SQL 执行效率,适用于 大数据处理。
DROP TABLE stu PURGE; -- if exists
-------------- 清空表,方便测试 ------------------
CREATE TABLE stu (
s_id NUMBER(3),
s_xm VARCHAR2(30)
);
ALTER TABLE stu ADD CONSTRAINT pk_stu_id PRIMARY KEY (s_id) ;
INSERT ALL
INTO stu(s_id, s_xm) VALUES (1, '小游子')
INTO stu(s_id, s_xm) VALUES (2, '小优子')
SELECT 1 FROM dual; -- 循环插入的次数
COMMIT;
总共 4 个步骤,缺一不可:(参数可选)
DECLARE
CURSOR cur_stu(参数值 参数类型) IS SELECT * FROM stu t [WHERE t.id = 参数值]; -- 步骤1: 声明游标
v_stu cur_stu%ROWTYPE;
BEGIN
OPEN cur_stu(参数值); -- 步骤2: 打开游标
LOOP
FETCH cur_stu INTO v_stu; -- 步骤3: 提取数据
EXIT WHEN cur_stu%NOTFOUND;
dbms_output.put_line(v_stu.s_id ||' : '||v_stu.s_xm);
END LOOP;
CLOSE cur_stu; -- 步骤4: 关闭游标
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
DECLARE
v_sql VARCHAR(2000);
v_b1 NUMBER(3) := 3;
v_id system.stu.s_id%TYPE;
v_xm system.stu.s_xm%TYPE;
-- TYPE cur_stu_type IS REF CURSOR;
-- cur_stu cur_stu_type;
cur_stu SYS_REFCURSOR;
BEGIN
v_sql := 'SELECT t.s_id, t.s_xm FROM stu t WHERE t.s_id <= :b1';
OPEN cur_stu FOR v_sql
USING v_b1; -- 绑定变量 : 大数据处理常用优化手段
LOOP
FETCH cur_stu
INTO v_id, v_xm;
EXIT WHEN cur_stu%NOTFOUND;
dbms_output.put_line('序号:' || v_id || chr(10) || '姓名:' || v_xm);
END LOOP;
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
属性 | 返回值类型 | 说明 |
---|---|---|
SQL%ISOPEN | 布尔型 | 游标是否开启 , true:开启,false:关闭 |
SQL%FOUND | 布尔型 | 前一个 fetch 语句是否有值 ,true:有,false:没有 |
SQL%NOTFOUND | 布尔型 | 与上述相反,常被用于 退出循环 ,true:有,false:没有, null : 空。注意哦,只有 为 true 时,才退出(当 第一次 fetch 为 null 时,不会退出!) |
SQL%ROWCOUNT | 整型 | 当前成功执行的数据行数(非总记录数 ) |
DECLARE
CURSOR cur_stu IS SELECT * FROM stu;
v_stu cur_stu%ROWTYPE;
BEGIN
OPEN cur_stu;
LOOP
FETCH cur_stu INTO v_stu;
EXIT WHEN cur_stu%NOTFOUND; -- sql%notfound
IF cur_stu%FOUND THEN -- sql%found
dbms_output.put_line(v_stu.s_id || ' : ' || v_stu.s_xm);
dbms_output.put_line('当前记录条数:' || cur_stu%ROWCOUNT); -- sql%rowcount
ELSE
dbms_output.put_line('无记录...');
END IF;
END LOOP;
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
IF cur_stu%ISOPEN THEN -- sql%isopen
CLOSE cur_stu;
END IF;
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
Oracle 官方文档解释:
Before the first fetch%NOTFOUND returns NULL
. If fetch never executes susscessfully. the loop is never exited, because then EXIT WHEN statement executes only if it’s WHEN condition is true. To be safe. you might want to use the following EXIT statement instead:
EXIT WHEN SQL%NOTFOUND OR SQL%NOTFOUND IS NULL;
DECLARE
CURSOR cur_stu IS SELECT * FROM stu;
v_stu cur_stu%ROWTYPE;
BEGIN
OPEN cur_stu;
LOOP
-- 注意哦,第一次时 cur_stu 下的记录 初始值均为 null,因为还未提取数据
-- 还有,最后一次时 cur_stu 下的记录 也为 null,因为 数据已经提取完毕,且 fetch .. 位于 sql%notfound 之后!
EXIT WHEN cur_stu%NOTFOUND;
FETCH cur_stu INTO v_stu; -- before the first fetch...
dbms_output.put_line(v_stu.s_id || ' : ' || v_stu.s_xm);
END LOOP;
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
测试结果:
疑惑 / 解释:
总共只有 2 条记录,我们都 fetch 之后,按理说游标已经空了,那么第三次应该是 fetch 的空值,为什么还是输出 ‘2 : 小优子’ 呢?
1、fetch … into 语句有数据时,会覆盖 into 变量后的值
2、fetch … into 语句无数据时,into 变量的值不改变(为最后一次有数据的值),就像 select … into 如果没有数据会报异常,但是不会把 into 后面的变量置为空一样。
可参考:
DECLARE
CURSOR cur_stu IS SELECT s_xm FROM stu WHERE 1 = 2; -- 限制 无记录
v_stu system.stu.s_xm%TYPE;
BEGIN
OPEN cur_stu;
v_stu := '小倩子';
FETCH cur_stu INTO v_stu;
dbms_output.put_line('此时的 v_stu: '||v_stu);
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
在 PL/SQL 中使用
DML 和 select into
时,会自动创建隐式游标,隐式游标自动声明、打开和关闭(无法手动查看
),其名为 SQL,通过检查隐式游标的属性可以获得最近
执行的 DML 和 select into 语句的信息
DECLARE
v_count NUMBER;
BEGIN
INSERT INTO stu(s_id, s_xm) VALUES(3, '小倩子');
IF SQL%FOUND THEN
dbms_output.put_line('插入成功!');
END IF;
UPDATE stu t SET t.s_xm = '小王子' WHERE t.s_id = 3;
IF SQL%FOUND THEN
dbms_output.put_line('更新成功!');
END IF;
DELETE FROM stu t WHERE t.s_id = 3;
IF SQL%FOUND THEN
dbms_output.put_line('删除成功!');
END IF;
SELECT COUNT(1) INTO v_count FROM stu t;
IF SQL%FOUND THEN
dbms_output.put_line('总记录为: '||v_count);
END IF;
IF SQL%ISOPEN THEN
dbms_output.put_line('可手动查看');
ELSE
dbms_output.put_line('无法手动查看');
END IF;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
参考本页:2.1.1 静态游标(参数可选)
【1、弱类型、无 return】
DECLARE
v_sql VARCHAR(2000);
v_b1 NUMBER(3) := 3;
v_id system.stu.s_id%TYPE;
v_xm system.stu.s_xm%TYPE;
TYPE cur_stu_type IS REF CURSOR;
cur_stu cur_stu_type;
BEGIN
v_sql := 'SELECT t.s_id, t.s_xm FROM stu t WHERE t.s_id <= :b1';
OPEN cur_stu FOR v_sql
USING v_b1; -- 绑定变量 : 大数据处理常用优化手段
LOOP
FETCH cur_stu
INTO v_id, v_xm;
EXIT WHEN cur_stu%NOTFOUND;
dbms_output.put_line('序号:' || v_id || chr(10) || '姓名:' || v_xm);
END LOOP;
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
【2、强类型,有 return】
open cur… for 时,有两点需要注意:
1、for 后是 SQL语句(而不能是 字符串)
2、cur… 必须和 return 的 类型完全一致
3、无法使用 绑定变量
DECLARE
-- v_sql VARCHAR(2000);
-- v_b1 NUMBER(3) := 3;
-- v_id system.stu.s_id%TYPE;
-- v_xm system.stu.s_xm%TYPE;
v_stu system.stu%ROWTYPE;
TYPE cur_stu_type IS REF CURSOR RETURN system.stu%ROWTYPE;
cur_stu cur_stu_type;
BEGIN
-- v_sql := 'SELECT t.s_id, t.s_xm FROM stu t WHERE t.s_id <= :b1';
OPEN cur_stu FOR SELECT t.s_id, t.s_xm FROM stu t;
LOOP
FETCH cur_stu
INTO v_stu; --v_id, v_xm;
EXIT WHEN cur_stu%NOTFOUND;
dbms_output.put_line('序号:' || v_stu.s_id || chr(10) || '姓名:' || v_stu.s_xm);
END LOOP;
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
我常用的,写法简洁,效果完全同
弱类型 ref
参考本页:2.1.2 动态游标
结论:一般来说批量处理的速度要最好,隐式游标的次之,单条处理的最差
1、批量处理
open 游标;
loop
fetch 游标 bulk collect into 集合变量(也就是 table 类型哦) limit 数值; -- 一般 500 左右
exit when 条件 --(变量.count = 0,如果用 sql%notfound 不足 limit 的记录就不会被执行哦)
close 游标;
2、隐式游标
for x in (sql 语句) loop
... 逻辑处理
end loop;
3、单条处理
open 游标;
loop
fetch 游标 into 变量;
exit when 条件
end loop;
close 游标;
实际开发常用:
PS: 如果对 table 类型、record 类型有疑问,请点击 %type、%rowtype,varry、record、table 的使用详解
DECLARE
v_sql VARCHAR(2000);
v_b1 NUMBER(3) := 3;
TYPE record_stu IS RECORD(
v_id system.stu.s_id%TYPE,
v_xm system.stu.s_xm%TYPE);
TYPE table_stu IS TABLE OF record_stu;
v_stu table_stu;
cur_stu SYS_REFCURSOR;
BEGIN
v_sql := 'SELECT t.s_id, t.s_xm FROM stu t WHERE t.s_id <= :b1';
OPEN cur_stu FOR v_sql
USING v_b1; -- 绑定变量 : 大数据处理常用优化手段
LOOP
FETCH cur_stu BULK COLLECT
INTO v_stu LIMIT 1; -- 数据量太少,仅当前测试使用哦,实际开发 建议 500 左右
EXIT WHEN v_stu.count = 0;
FOR i IN v_stu.first .. v_stu.last LOOP
dbms_output.put_line('序号:' || v_stu(i).v_id || ' , ' || '姓名:' || v_stu(i).v_xm);
END LOOP;
END LOOP;
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;