ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

Oracle 游标详解 【转载至CSDN「鱼丸丶粗面」】

2019-12-26 09:01:47  阅读:269  来源: 互联网

标签:dbms cur 游标 鱼丸 stu put output 粗面


文章目录
1、概念
1.1 游标是什么?
1.2 游标的作用?
1.3 游标结构图
1.4 基础数据准备
2、语法及属性
2.1 语法
2.1.1 静态游标
2.1.2 动态游标
2.2 属性
2.2.1 特别说明 sql%notfound
3、分类
3.1 静态游标
3.1.1 隐式游标 dml
3.1.2 显示游标 cursor
3.2 动态游标
3.2.1 自定义类型 ref
3.2.2 系统类型 SYS_REFCURSOR
4、效率
4.1 三种游标循环效率对比
1、概念
1.1 游标是什么?
位于内存中的 "临时表"。 具体如下:游标是从数据表中提取出来的数据,以 临时表 的形式存放到 内存中,在游标中有一个 数据指针, 在初始状态下指向的是首记录,利用 fetch 语句可以移动该指针,从而对游标中的数据进行各种操作,然后将操作结果写回到数据库中。

 


 

1.2 游标的作用?
1、用来查询数据库,获取记录集合(结果集)的指针,可以让开发者 一次访问一行结果集, 在每条结果集上作操作。
2、用 ‘牺牲内存’ 来提升 SQL 执行效率,适用于 大数据处理。

1.3 游标结构图

 


 

1.4 基础数据准备
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;

2、语法及属性
2.1 语法

2.1.1 静态游标
总共 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;



2.1.2 动态游标
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;

2.2 属性
属性    返回值类型    说明
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; 2.2.1 特别说明 sql%notfound 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;

测试结果 :

 


 

3、分类
3.1 静态游标
3.1.1 隐式游标 dml
在 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;

 

 

3.1.2 显示游标 cursor
参考本页:2.1.1 静态游标(参数可选)

3.2 动态游标
3.2.1 自定义类型 ref
【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;

3.2.2 系统类型 SYS_REFCURSOR
我常用的,写法简洁,效果完全同 弱类型 ref

参考本页:2.1.2 动态游标

4、效率
4.1 三种游标循环效率对比
结论:一般来说批量处理的速度要最好,隐式游标的次之,单条处理的最差

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;
———————————————— 版权声明:本文为CSDN博主「鱼丸丶粗面」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_34745941/article/details/81294166

 

标签:dbms,cur,游标,鱼丸,stu,put,output,粗面
来源: https://www.cnblogs.com/zouhao/p/12100196.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有