FORALL全解析

来源:互联网 发布:巨人网络收购能过会吗 编辑:程序博客网 时间:2024/05/23 20:43

  FORALL语句会从PL/SQL引擎会向SQL引擎发送SQL语句,后者会向PL/SQL引擎返回结果。PL/SQL和SQL引擎之间的通信称为上下文切换。这种上下文切换存在一定的性能负载。


1、FORALL语句

  FORALL语句会从PL/SQL引擎向SQL引擎批量发送INSERT、UPDATE和DELETE语句,而不是每次发送一条语句。例如,考虑下面的数值型FOR循环会10次迭代执行INSERT语句:

FOR i IN 1..10 LOOP

    INSERT INTO table_name

    VALUES (...);

END LOOP;


  该INSERT语句会从PL/SQL引擎发送到SQL引擎10次。也就是说,会发生10次上下文其换。如果使用FORALL语句替换这个FOR循环,只需要发送一次INSERT语句,但是会执行10次。在这种情况下,在PL/SQL和SQL之间只会发生一次上下文切换。


FORALL语句具有如下结构

  1. FORALL loop_counter IN bounds_clause
  2. SQL_STATEMENT [SAVE EXCEPTIONS];

其中,bounds_clause是下面形式之一

  1. lower_limit..upper_limit  就是 1..10
  2. INDICES OF collection_name BETWEEN lower_limit..upper_limit
  3. VALUES OF collection_name

后面这俩,第二种形式INDICES OF会引用特定集合中单个元素的下标。这个集合也许是嵌套表,或者具有数字下标的联合数组

第三种形式VALUES OF会引用特定集合中单个元素的值,这个集合可能是嵌套表或者联合数组。

  接下来,SQL_STATEMENT是引用一个或者多个集合的静态或者动态的INSERT、UPDATE或者DELETE语句。最后,即使当SQL_STATEMENT导致异常时,选项SAVE EXCEPTIONS仍旧能够保证FORALL语句继续执行


实验前创建表

CREATE TABLE test(row_num number,row_text varchar2(10));

  1. declare
  2.    type row_num_type is table of number index by simple_integer;
  3.    type row_text_type is table of varchar2(10) index by simple_integer;
  4.    row_num_tab row_num_type;
  5.    row_text_tab row_text_type;
  6.    v_total number;
  7. begin
  8.    for i in 1..10 loop
  9.        row_num_tab(i) := i;
  10.        row_text_tab(i) := 'row'||i;
  11.    end loop;
  12.    forall i in 1..10
  13.        insert into test(row_num,row_text)
  14.        values (row_num_tab(i),row_text_tab(i));
  15.    commit;
  16.    select count(*) into v_total from test;
  17.    dbms_output.put_line(v_total||' rows insterted');
  18. end;
  19. /

结合动态sql

  1. declare
  2.    type row_num_type is table of number index by simple_integer;
  3.    type row_text_type is table of varchar2(10) index by simple_integer;
  4.    row_num_tab row_num_type;
  5.    row_text_tab row_text_type;
  6.    v_total number;
  7.    sql_stmt varchar2(300);
  8. begin
  9.    for i in 1..10 loop
  10.        row_num_tab(i) := i;
  11.        row_text_tab(i) := 'row'||i;
  12.    end loop;
  13.    sql_stmt := 'insert into test(row_num,row_text) values (:1,:2)';
  14.    forall i in 1..10
  15.        execute immediate sql_stmt using row_num_tab(i),row_text_tab(i);
  16.    commit;
  17.    select count(*) into v_total from test;
  18.    dbms_output.put_line(v_total||' rows insterted');
  19. end;
  20. /
  21. 10 rows insterted
  22. PL/SQL procedure successfully completed.


  

比较for loop和forall statement时间差异

这里用到dbms_utility.get_time包,精度为百分之1秒

  1. declare
  2.    type row_num_type is table of number index by simple_integer;
  3.    type row_text_type is table of varchar2(10) index by simple_integer;
  4.    row_num_tab row_num_type;
  5.    row_text_tab row_text_type;
  6.    v_total number;
  7.    v_start_time integer;
  8.    v_end_time integer;
  9. begin
  10.    for i in 1..1000 loop
  11.        row_num_tab(i) := i;
  12.        row_text_tab(i) := 'row'||i;
  13.    end loop;
  14.    v_start_time := dbms_utility.get_time;
  15.    dbms_output.put_line('v_start_time :'||v_start_time);
  16.    for i in 1..1000 loop
  17.        insert into test(row_num,row_text)
  18.        values (row_num_tab(i),row_text_tab(i));
  19.    end loop;
  20.    v_end_time    := dbms_utility.get_time;
  21.    dbms_output.put_line('v_end_time :'||v_end_time);
  22.    dbms_output.put_line('duration of the for loop '||(v_end_time - v_start_time));
  23.    select count(*) into v_total from test;
  24.    dbms_output.put_line(v_total||' rows insterted');
  25.    v_start_time := dbms_utility.get_time;
  26.    dbms_output.put_line('v_start_time :'||v_start_time);
  27.    forall i in 1..1000
  28.        insert into test(row_num,row_text)
  29.        values (row_num_tab(i),row_text_tab(i));
  30.    commit;
  31.    v_end_time    := dbms_utility.get_time;
  32.    dbms_output.put_line('v_end_time :'||v_end_time);
  33.    dbms_output.put_line('duration of the forall statement '||(v_end_time - v_start_time));
  34.    select count(*) into v_total from test;
  35.    dbms_output.put_line(v_total||' rows insterted');
  36.    commit;
  37. end;
  38. /
  39. v_start_time :449982608
  40. v_end_time :449982611
  41. duration of the for loop 3
  42. 1000 rows insterted
  43. v_start_time :449982611
  44. v_end_time :449982611
  45. duration of the forall statement 0
  46. 2000 rows insterted



1.1 SAVE EXECPTIONS选项

  使用save exceptions选项能够实现:即使当对应的sql语句导致异常,forall语句仍旧能够继续执行。这些异常被存储在名为SQL%BULK_EXCEPTIONS的游标属性中。SQL%BULK_EXCEPTIONS游标属性是个记录集合,其中每个记录由两个字段组成:ERROR_INDEX和ERROR_CODE。ERROR_INDEX字段会存储发生异常的FORALL语句的迭代编号,ERROR_CODE会存储对应于抛出异常的oracle错误代码

  可以是用SQL%BULK_EXCEPTIONS.COUNT来检索FORALL语句执行过程中所发生的异常数量。注意,一贯不会保存单个的错误消息,但是可以使用SQLERRM函数进行查询。

  1. TRUNCATE TABLE TEST;
  2. DECLARE
  3.  -- Define collection types and variables
  4.  TYPE row_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  5.  TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  6.  row_num_tab  row_num_type;
  7.  row_text_tab row_text_type;
  8.  -- Define user-defined exception and associated Oracle
  9.  -- error number with it
  10.  errors EXCEPTION;
  11.  PRAGMA EXCEPTION_INIT(errors, -24381);
  12. BEGIN
  13.  -- Populate collections
  14.  FOR i IN 1 .. 10 LOOP
  15.    row_num_tab(i) := i;
  16.    row_text_tab(i) := 'row ' || i;
  17.  END LOOP;
  18.  -- Modify 1, 5, and 7 elements of the V_ROW_TEXT collection
  19.  -- These rows will cause exception in the FORALL statement
  20.  row_text_tab(1) := RPAD(row_text_tab(1), 11, ' ');
  21.  row_text_tab(5) := RPAD(row_text_tab(5), 11, ' ');
  22.  row_text_tab(7) := RPAD(row_text_tab(7), 11, ' ');
  23.  -- Populate TEST table
  24.  FORALL i IN 1 .. 10 SAVE EXCEPTIONS /* 要是不写这个save exceptions就会直接抛出错误
  25.  ORA-12899: value too large for column "SCOTT"."TEST"."ROW_TEXT" (actual: 11, maximum: 10)*/
  26.    INSERT INTO test
  27.      (row_num, row_text)
  28.    VALUES
  29.      (row_num_tab(i), row_text_tab(i));
  30.  COMMIT;
  31. EXCEPTION
  32.  WHEN errors THEN
  33.    -- Display total number of exceptions encountered
  34.    DBMS_OUTPUT.PUT_LINE('There were ' || SQL%BULK_EXCEPTIONS.COUNT ||
  35.                         ' exceptions');
  36.    -- Display detailed exception information
  37.    FOR i in 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP
  38.      DBMS_OUTPUT.PUT_LINE('Record ' || SQL%BULK_EXCEPTIONS(i).error_index ||
  39.                           ' caused error ' || i || ': ' || SQL%BULK_EXCEPTIONS(i)
  40.                           .ERROR_CODE || ' ' ||
  41.                           SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
  42.    END LOOP;
  43. END;
  44. /
  45. There were 3 exceptions
  46. Record 1 caused error 1: 12899 ORA-12899: value too large for column  (actual: , maximum: )
  47. Record 5 caused error 2: 12899 ORA-12899: value too large for column  (actual: , maximum: )
  48. Record 7 caused error 3: 12899 ORA-12899: value too large for column  (actual: , maximum: )


1.2 INDICES OF

正如前面所提到的那样,使用INDICES OF选项,可以循环处理稀疏的集合,回想一下,这种集合也许是嵌套表,或者联合数组。

引用的是下标

  1. declare
  2.  TYPE row_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  3.  TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  4.  row_num_tab  row_num_type;
  5.  row_text_tab row_text_type;
  6.  v_total number;
  7. begin
  8.    FOR i IN 1 .. 10 LOOP
  9.        row_num_tab(i) := i;
  10.        row_text_tab(i) := 'row ' || i;
  11.      END LOOP;
  12.      row_num_tab.DELETE(1);row_text_tab.DELETE(1);
  13.      row_num_tab.DELETE(3);row_text_tab.DELETE(3);
  14.      row_num_tab.DELETE(5);row_text_tab.DELETE(5);
  15.      FORALL i in INDICES OF row_num_tab
  16.          insert into test(row_num,row_text)
  17.          values(row_num_tab(i),row_text_tab(i));
  18.      commit;
  19.      select count(*) into v_total from test;
  20.      dbms_output.put_line('There are '||v_total||' rows in the test table');
  21. end;
  22. /
  23. There are 7 rows in the test table
  24. PL/SQL procedure successfully completed.
  25. scott@ORCL>select * from test;
  26.   ROW_NUM ROW_TEXT
  27. ---------- ----------
  28.         2 row 2
  29.         4 row 4
  30.         6 row 6
  31.         7 row 7
  32.         8 row 8
  33.         9 row 9
  34.        10 row 10
  35. 7 rows selected.

为让当前嵌套变得稀疏,删除第1,3,5元素,这样的化,FORALL语句会迭代7次,第7行数据会添加到TEST表。下面的输出可以说明这一点

here are 7 rows in the test table


PL/SQL procedure successfully completed.


1.3 VALUES OF选项

VALUES OF用于一种不同的情况:绑定数组可以是稀疏数组,也可以不是,但我只想使用该数组中元素的一个子集。那么我就可以使用VALUES OF来指向我希望在DML操作中使用的值。

VALUES OF最关键的是他引用的是特定集合中单个元素的值

  1. SQL> declare
  2.  2    TYPE row_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  3.  TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  4.  row_num_tab  row_num_type;
  5.  row_text_tab row_text_type;
  6. begin
  7. FOR i IN 1 .. 10 LOOP
  8.    row_num_tab(i) := i;
  9.    row_text_tab(i) := 'row ' || i;
  10.  END LOOP;
  11. 11  
  12.  row_num_tab.DELETE(1);row_text_tab.DELETE(1);
  13.  row_num_tab.DELETE(3);row_text_tab.DELETE(3);
  14.  row_num_tab.DELETE(5);row_text_tab.DELETE(5);
  15. 15  
  16.  FORALL i in values OF row_num_tab
  17.  insert into test(row_num,row_text)
  18.  values(row_num_tab(i),row_text_tab(i));
  19.  commit;
  20. end;
  21. 21   /
  22.  TYPE row_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  23.                       *
  24. ERROR at line 2:
  25. ORA-06550: line 2, column 24:
  26. PLS-00667: Element type of associative array should be pls_integer or binary_integer
  27. ORA-06550: line 16, column 15:
  28. PL/SQL: Statement ignored

values of后面跟的集合必须是pls_integer?

事实上values of使用的是联合数组,他必须使用PLS_INTEGER或BINARY_INTEGER进行索引

  1. create table test_exc(row_num number,row_text varchar2(50));
  2. DECLARE
  3.   -- Define collection types and variables
  4.   TYPE row_num_type  IS TABLE OF NUMBER       INDEX BY PLS_INTEGER;
  5.   TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  6.   TYPE exc_ind_type  IS TABLE OF PLS_INTEGER  INDEX BY PLS_INTEGER;
  7.   row_num_tab  row_num_type;
  8.   row_text_tab row_text_type;
  9.   exc_ind_tab  exc_ind_type;
  10.   -- Define user-defined exception and associated Oracle
  11.   -- error number with it
  12.   errors EXCEPTION;
  13.   PRAGMA EXCEPTION_INIT(errors, -24381);
  14. BEGIN
  15.   -- Populate collections
  16.   FOR i IN 1..10 LOOP
  17.      row_num_tab(i)  := i;
  18.      row_text_tab(i) := 'row '||i;
  19.   END LOOP;
  20.   -- Modify 1, 5, and 7 elements of the V_ROW_TEXT collection
  21.   -- These rows will cause exception in the FORALL statement
  22.   row_text_tab(1) := RPAD(row_text_tab(1), 11, ' ');
  23.   row_text_tab(5) := RPAD(row_text_tab(5), 11, ' ');
  24.   row_text_tab(7) := RPAD(row_text_tab(7), 11, ' ');
  25.   -- Populate TEST table
  26.   FORALL i IN 1..10 SAVE EXCEPTIONS
  27.      INSERT INTO test (row_num, row_text)
  28.      VALUES (row_num_tab(i), row_text_tab(i));
  29. COMMIT;
  30. EXCEPTION
  31.   WHEN errors THEN
  32.      -- Populate V_EXC_IND_TAB collection to be used in the VALUES
  33.      -- OF clause
  34.      FOR i in 1.. SQL%BULK_EXCEPTIONS.COUNT LOOP
  35.         exc_ind_tab(i) := SQL%BULK_EXCEPTIONS(i).error_index;
  36.      END LOOP;
  37.      -- Insert records that caused exceptions in the TEST_EXC
  38.      -- table
  39.      FORALL i in VALUES OF exc_ind_tab
  40.         INSERT INTO test_exc (row_num, row_text)
  41.         VALUES (row_num_tab(i), row_text_tab(i));
  42.   COMMIT;
  43. END;
  44. /
  45. scott@ORCL>select * from test;
  46.   ROW_NUM ROW_TEXT
  47. ---------- ----------
  48.         2 row 2
  49.         3 row 3
  50.         4 row 4
  51.         6 row 6
  52.         8 row 8
  53.         9 row 9
  54.        10 row 10
  55. 7 rows selected.
  56. test表插入的值应该没什么好解释的
  57. scott@ORCL>select * from test_exc;
  58.   ROW_NUM ROW_TEXT
  59. ---------- --------------------------------------------------
  60.         1 row 1
  61.         5 row 5
  62.         7 row 7
  63. values of引用的是集合的值,所以i是在exc_ind_tab的值中循环
  64. exc_ind_tab(1) = 1
  65. exc_ind_tab(2) = 5
  66. exc_ind_tab(3) = 7
  67. 所以i in 1,5,7
  68. row_num_tab(1) = 1;row_text_tab(1) = row 1;
  69. row_num_tab(5) = 5;row_text_tab(5) = row 5;
  70. row_num_tab(7) = 7;row_text_tab(7) = row 7;
  71. 所以一个批量插入FORALL 异常模板可以是这样
  72. 1.输出错误信息
  73. 2.将错误信息插入err_log错日志表
  74. 3.将出错的行,插入text_exc
  75. 创建一个err_log
  76. create table err_log(id number,msg varchar2(500));
  77. DECLARE
  78.   errors EXCEPTION;
  79.   PRAGMA EXCEPTION_INIT(errors, -24381);
  80. BEGIN
  81. EXCEPTION
  82. WHEN errors THEN
  83. for i in 1..SQL%BULK_EXCEPTIONS.COUNT loop
  84.    dbms_output.put_line('line '||SQL%BULK_EXCEPTIONS(i).INDEX_CODE||' has error. error is '||sqlerrm(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
  85.    insert into err_log values(SQL%BULK_EXCEPTIONS(i).INDEX_CODE,sqlerrm(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
  86.    exc_ind_tab(i) := SQL%BULK_EXCEPTIONS(i).error_index;
  87. end loop;
  88. commit;
  89. /* 把出问题的值插入test_exc表*/
  90. forall i in values of exc_ind_tab
  91. INSERT INTO test_exc (row_num, row_text) VALUES (row_num_tab(i), row_text_tab(i));
  92.   COMMIT;
  93. END;
  94. /


我的实验


实验1:

INDICES OF不是稀疏的也可以

  1. declare
  2.  TYPE row_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  3.  TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  4.  row_num_tab  row_num_type;
  5.  row_text_tab row_text_type;
  6. begin
  7.    FOR i IN 1 .. 10 LOOP
  8.        row_num_tab(i) := i;
  9.        row_text_tab(i) := 'row ' || i;
  10.      END LOOP;
  11.      row_num_tab.DELETE(1);row_text_tab.DELETE(1);
  12.      row_num_tab.DELETE(3);row_text_tab.DELETE(3);
  13.      row_num_tab.DELETE(5);row_text_tab.DELETE(5);
  14.      FORALL i in INDICES OF row_num_tab
  15.          insert into test(row_num,row_text)
  16.          values(row_num_tab(i),row_text_tab(i));
  17.      commit;
  18. end;
  19. /
  20. declare
  21.  TYPE row_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  22.  TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  23.  row_num_tab  row_num_type;
  24.  row_text_tab row_text_type;
  25. begin
  26.    FOR i IN 1 .. 10 LOOP
  27.        row_num_tab(i) := i;
  28.        row_text_tab(i) := 'row ' || i;
  29.      END LOOP;
  30.      FORALL i in INDICES OF row_num_tab
  31.          insert into test(row_num,row_text)
  32.          values(row_num_tab(i),row_text_tab(i));
  33.      commit;
  34. end;
  35. /
  36. PL/SQL procedure successfully completed.
  37. SQL> select * from test;
  38.   ROW_NUM ROW_TEXT
  39. ---------- ----------
  40.         1 row 1
  41.         2 row 2
  42.         3 row 3
  43.         4 row 4
  44.         5 row 5
  45.         6 row 6
  46.         7 row 7
  47.         8 row 8
  48.         9 row 9
  49.        10 row 10
  50. 10 rows selected.


实验2:

  1. declare
  2.  TYPE row_num_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  3.  TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  4.  row_num_tab  row_num_type;
  5.  row_text_tab row_text_type;
  6. begin
  7.        row_num_tab(1) := 10;
  8.        row_text_tab(1) := 'row ' || 10;
  9.        row_num_tab(2) := 9;
  10.        row_text_tab(2) := 'row ' || 9;
  11.        row_num_tab(3) := 8;
  12.        row_text_tab(3) := 'row ' || 8;
  13.        row_num_tab(4) := 7;
  14.        row_text_tab(4) := 'row ' || 7;
  15.        row_num_tab(5) := 6;
  16.        row_text_tab(5) := 'row ' || 6;
  17.        row_num_tab(6) := 5;
  18.        row_text_tab(6) := 'row ' || 5;
  19.        row_num_tab(7) := 4;
  20.        row_text_tab(7) := 'row ' || 4;
  21.        row_num_tab(8) := 3;
  22.        row_text_tab(8) := 'row ' || 3;
  23.        row_num_tab(9) := 2;
  24.        row_text_tab(9) := 'row ' || 2;
  25.        row_num_tab(10) := 1;
  26.        row_text_tab(10) := 'row ' || 1;
  27.      FORALL i in INDICES OF row_num_tab
  28.          insert into test(row_num,row_text)
  29.          values(row_num_tab(i),row_text_tab(i));
  30.      commit;
  31. end;
  32. /
  33. SQL> select * from test;
  34.   ROW_NUM ROW_TEXT
  35. ---------- ----------
  36.        10 row 10
  37.         9 row 9
  38.         8 row 8
  39.         7 row 7
  40.         6 row 6
  41.         5 row 5
  42.         4 row 4
  43.         3 row 3
  44.         2 row 2
  45.         1 row 1
  46. 10 rows selected.

FORALL i in INDICES OF row_num_tab

这个i是在 row_num_tab集合的下表中循环,下表就是1~10

否则如果是值得话,第一行就是row_text_tab(10),显然不是这样


实验3:

  1. declare
  2.  TYPE row_num_type IS TABLE OF pls_integer INDEX BY PLS_INTEGER;
  3.  TYPE row_text_type IS TABLE OF VARCHAR2(11) INDEX BY PLS_INTEGER;
  4.  row_num_tab  row_num_type;
  5.  row_text_tab row_text_type;
  6. begin
  7.        row_num_tab(1) := 10;
  8.        row_text_tab(1) := 'row ' || 10;
  9.        row_num_tab(2) := 9;
  10.        row_text_tab(2) := 'row ' || 9;
  11.        row_num_tab(3) := 8;
  12.        row_text_tab(3) := 'row ' || 8;
  13.        row_num_tab(4) := 7;
  14.        row_text_tab(4) := 'row ' || 7;
  15.        row_num_tab(5) := 6;
  16.        row_text_tab(5) := 'row ' || 6;
  17.        row_num_tab(6) := 5;
  18.        row_text_tab(6) := 'row ' || 5;
  19.        row_num_tab(7) := 4;
  20.        row_text_tab(7) := 'row ' || 4;
  21.        row_num_tab(8) := 3;
  22.        row_text_tab(8) := 'row ' || 3;
  23.        row_num_tab(9) := 2;
  24.        row_text_tab(9) := 'row ' || 2;
  25.        row_num_tab(10) := 1;
  26.        row_text_tab(10) := 'row ' || 1;
  27.      FORALL i in values OF row_num_tab
  28.          insert into test(row_num,row_text)
  29.          values(row_num_tab(i),row_text_tab(i));
  30.      commit;
  31. end;
  32. /
  33. PL/SQL procedure successfully completed.
  34. SQL> select * from test;
  35.   ROW_NUM ROW_TEXT
  36. ---------- ----------
  37.         1 row 1
  38.         2 row 2
  39.         3 row 3
  40.         4 row 4
  41.         5 row 5
  42.         6 row 6
  43.         7 row 7
  44.         8 row 8
  45.         9 row 9
  46.        10 row 10
  47. 10 rows selected.

FORALL i in values OF row_num_tab

这个i引用的是元素值所以他是在10~1中循环

而不是下标

第一行显然引用的是row_num_tab(10),row_text_tab(10)


而10是元素值

row_num_tab(1) := 10;

row_text_tab(1) := 'row ' || 10;






0 0
原创粉丝点击