Oracle forall 详解

1、概念

1.1 思维导图

在这里插入图片描述

1.2 数据准备

DROP TABLE stu; -- if exists

CREATE TABLE stu(
   s_id NUMBER,
   s_xm VARCHAR2(10)
);

2、forall 的三种语法

2.1 forall i in 下限 … 上限

-- 语法1:
FORALL 下标变量(只能用作下标) IN 下限 .. 上限
   SQL 语句。 -- 只允许一条 sql 语句

2.1.1 批量插入

DECLARE
   TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;

   v_stu_array stu_table;

BEGIN
   FOR i IN 1 .. 10 LOOP
      v_stu_array(i).s_id := i;
      v_stu_array(i).s_xm := 'a' || i;
   END LOOP;

   FORALL i IN v_stu_array.first .. v_stu_array.last
      INSERT INTO stu VALUES v_stu_array(i);
-- 等同于 
-- INSERT INTO stu(s_id, s_xm) VALUES(v_stu_array(i).s_id, v_stu_array(i).s_xm);
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

2.1.2 批量修改

DECLARE
   TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;

   v_stu_array stu_table;

BEGIN
   FOR i IN 1 .. 10 LOOP
      v_stu_array(i).s_id := i;
      v_stu_array(i).s_xm := 'a' || i;
   END LOOP;

   FORALL i IN v_stu_array.first .. v_stu_array.last
      UPDATE stu t SET t.s_xm = 'b' WHERE t.s_id = v_stu_array(i).s_id;
   -- 下句会报错!(该上下文中不允许使用 forall 循环变量!)
   -- UPDATE stu t SET t.s_xm = 'b'|| i WHERE t.s_id = v_stu_array(i).s_id;      
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

2.1.3 批量删除

DECLARE
   TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;

   v_stu_array stu_table;

BEGIN
   FOR i IN 1 .. 10 LOOP
      v_stu_array(i).s_id := i;
      v_stu_array(i).s_xm := 'a' || i;
   END LOOP;

   FORALL i IN v_stu_array.first .. v_stu_array.last
      DELETE FROM stu t WHERE t.s_id = v_stu_array(i).s_id;

EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

2.2 forall i in indices of 集合

--语法2:  
FORALL 下标变量 IN INDICES OF(跳过没有赋值的元素,例如被 DELETE 的元素,NULL 也算值) 集合 
   sql 语句;
DECLARE
   TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;

   v_stu_array stu_table;

BEGIN
   FOR i IN 1 .. 10 LOOP
      v_stu_array(i).s_id := i;
      v_stu_array(i).s_xm := 'a' || i;
   END LOOP;

   v_stu_array.delete(3);
   v_stu_array(6) := NULL;
   v_stu_array.delete(9);
   
   FORALL i IN INDICES OF v_stu_array
      INSERT INTO stu VALUES v_stu_array(i);

EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

测试结果:
在这里插入图片描述

2.3 forall i in values of pls_ 、binary_

--语法3:  
FORALL 下标变量 IN VALUES OF 集合(把该集合中的值当作下标,且该集合值的类型只能是 PLS_INTEGER、BINARY_INTEGER)  
   sql 语句;  

DECLARE
   TYPE index_table IS TABLE OF PLS_INTEGER;
   TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;

   v_index_array index_table;
   v_stu_array   stu_table;

BEGIN
   FOR i IN 1 .. 10 LOOP
      v_stu_array(i).s_id := i;
      v_stu_array(i).s_xm := 'a' || i;
   END LOOP;

   v_index_array := index_table(1, 3, 5, 7, 9);
   
   FORALL i IN VALUES OF v_index_array
      INSERT INTO stu VALUES v_stu_array(i);

EXCEPTION 
   WHEN OTHERS THEN
      dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

测试结果:

在这里插入图片描述

3、for loop、forall、绑定变量 效率对比

TRUNCATE TABLE stu; -- 清空数据,方便测试

CREATE TABLE stu1 AS SELECT * FROM stu WHERE 1 = 2;
CREATE TABLE stu2 AS SELECT * FROM stu WHERE 1 = 2;
CREATE TABLE stu3 AS SELECT * FROM stu WHERE 1 = 2;
--------- 以上为基础数据准备 ---------
DECLARE
   TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;

   v_stu_array stu_table;
   t  NUMBER;
   t1 NUMBER;
   t2 NUMBER;
   t3 NUMBER;
BEGIN
   FOR i IN 1 .. 500000 LOOP
      v_stu_array(i).s_id := i;
      v_stu_array(i).s_xm := 'a' || i;
   END LOOP;
   t  := dbms_utility.get_time;
   
   FOR i  IN 1 .. 500000 LOOP
      INSERT INTO stu1 VALUES v_stu_array(i);
   END LOOP;
   t1 := dbms_utility.get_time;

   FORALL i IN 1 .. 500000
      INSERT INTO stu2 VALUES v_stu_array(i);
   t2 := dbms_utility.get_time;
   
   FORALL i IN 1 .. 500000
      EXECUTE IMMEDIATE 'INSERT INTO stu3(s_id, s_xm) VALUES(:b1, :b2)' USING v_stu_array(i).s_id, v_stu_array(i).s_xm;
   t3 := dbms_utility.get_time;
   
   dbms_output.put_line('一般 for 循环用时:'|| to_char(t1 - t));
   dbms_output.put_line('一般 forall 循环用时:'|| to_char(t2 - t1));
   dbms_output.put_line('一般 forall + 绑定变量 循环用时:'|| to_char(t3 - t2));
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

测试结果:
在这里插入图片描述

4、forall 回滚及异常

4.1 forall 如何影响回滚

TRUNCATE TABLE stu;

INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子aaaa');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子');

COMMIT;
--- SELECT LENGTHB('小王子aaaa') from dual; 长度为 10, 为限定长度
DECLARE
   TYPE num_list_table IS TABLE OF NUMBER;
   v_num_list num_list_table := num_list_table(1, 2, 3, 4);
BEGIN
   FORALL j IN v_num_list.first .. v_num_list.last
      UPDATE stu t
         SET t.s_xm = t.s_xm || 'a'
       WHERE t.s_id = v_num_list(j);
EXCEPTION
   WHEN OTHERS THEN
      COMMIT; -- 如果此处不加 commit,则回滚所有记录
END;

运行结果:(第三个执行报错)
在这里插入图片描述

4.2 %bulk_rowcount 属性计算 forall 影响行数

   在进行 SQL 数据操作语句时,SQL 引擎开发一个隐式游标(命名 SQL),该游标的标量属性(Scalar Attribute)有 %found、%isopen、%notfound and %rowcountforall 语句除具有上边的标量属性外,还有个复合属性(Composite attribute):%bulk_rowcount,该属性具有 索引表(index by table)语法
   它的第 "i" 个元素储存 SQL 语句(insert、update、delete)第 "i" 个执行的处理行数。如果第 “i” 个执行 未影响 行,那么 %bulk_rowcount(i) 返回 0

  • %rowcount : 返回 SQL 语句所有执行处理的总行数
  • %found%notfound 仅与 SQL 语句的最后执行有关,但是,可以使用 %bulk_rowcount 推断单个执行的值。如 %bulk_rowcount(i) = 0 时%found 和 %notfound 分别是 false 和 true
TRUNCATE TABLE stu;

INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子');

COMMIT;
------------- 基础数据准备 ------------- 
DECLARE
   TYPE num_list_table IS TABLE OF NUMBER;
   v_num_list num_list_table := num_list_table(1, 2, 3, 4);
BEGIN
   FORALL j IN v_num_list.first .. v_num_list.last
      UPDATE stu t
         SET t.s_xm = t.s_xm || 'a'
       WHERE t.s_id = v_num_list(j);
   
   IF SQL%FOUND THEN
      dbms_output.put_line('sql%found is true');
   END IF;
      
   IF SQL%BULK_ROWCOUNT(4) = 1 THEN
      dbms_output.put_line('sql%bulk_rowcount(4) is true');
   END IF;
   
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(SQLCODE ||' : '||SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

运行结果:
在这里插入图片描述

4.3 %bulk_exceptions 属性处理 forall 异常

    在执行 forall 语句期间,PL/SQL 提供了一个处理异常的机制,该机制使批绑定(bulk-bind)操作能保存储存异常信息并继续执行。方法是:在 forall 语句中增加 sava exceptions 关键字

  • %bulk_exceptions(i).error_index : 存储在引发异常期间 forall 语句迭代(重复:iteration)
  • %bulk_exceptions(i).error_code : 存储相应的 Oracle 错误代码
  • %bulk_exceptions(i).count : 存储异常的数量(该属性来自 index-by)
TRUNCATE TABLE stu;

INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子aaaa');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子bbbb');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子cccc');

COMMIT;
------------- 基础数据准备 ------------- 
DECLARE
   TYPE num_list_table IS TABLE OF NUMBER;
   v_num_list num_list_table := num_list_table(1, 2, 3, 4);
   errors_count NUMBER;
BEGIN
   FORALL j IN v_num_list.first .. v_num_list.last SAVE EXCEPTIONS
      UPDATE stu t
         SET t.s_xm = t.s_xm || 'a'
       WHERE t.s_id = v_num_list(j);
EXCEPTION
   WHEN OTHERS THEN
      errors_count := SQL%bulk_exceptions.count;
      dbms_output.put_line('Number of errors is ' || errors_count);
   
      FOR i IN 1 .. errors_count LOOP
         dbms_output.put_line('Error i: ' || i || ' ,error_index: ' || SQL%BULK_EXCEPTIONS(i)
                              .error_index);
         dbms_output.put_line('Error' || i || ' ,error_code: ' || SQL%BULK_EXCEPTIONS(i)
                              .error_code);
      END LOOP;
END;

运行结果:
在这里插入图片描述

4.4 同时使用 forall + bulk collect

TRUNCATE TABLE stu;

INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子');

COMMIT;
------------- 基础数据准备 ------------- 
DECLARE
   TYPE num_list_table IS TABLE OF NUMBER;
   TYPE stu_id_table IS TABLE OF system.stu.s_id%TYPE;
   TYPE stu_xm_table IS TABLE OF system.stu.s_xm%TYPE;

   v_num_list num_list_table := num_list_table(1, 2, 3, 4);
   v_id_table stu_id_table;
   v_xm_table stu_xm_table;

BEGIN
   FORALL i IN v_num_list.first .. v_num_list.last
      DELETE FROM stu t
       WHERE t.s_id = v_num_list(i)
      RETURNING t.s_id, t.s_xm BULK COLLECT INTO v_id_table, v_xm_table;
   
   FOR i IN  v_xm_table.first .. v_xm_table.last LOOP
      dbms_output.put_line('删除的姓名:'||v_xm_table(i));
   END LOOP;  
EXCEPTION 
   WHEN OTHERS THEN 
      dbms_output.put_line(SQLCODE ||' : '||SQLERRM);
      dbms_output.put_line(dbms_utility.format_error_backtrace);
END;

运行结果:

在这里插入图片描述

发布了43 篇原创文章 · 获赞 32 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_34745941/article/details/86532810