Oracle 游标详解

文章目录

  • 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 语句可以移动该指针,从而对游标中的数据进行各种操作,然后将操作结果写回到数据库中。

Oracle 游标详解_第1张图片

1.2 游标的作用?

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

1.3 游标结构图

Oracle 游标详解_第2张图片

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;

测试结果:
Oracle 游标详解_第3张图片
疑惑 / 解释:
总共只有 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;

测试结果 :
Oracle 游标详解_第4张图片

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;

Oracle 游标详解_第5张图片

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;

Oracle 游标详解_第6张图片

【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;

你可能感兴趣的:(#,Oracle,-,对象)