本文主要翻译、整理了ORACLE官方文档上有关FORALL的部份内容,不妥之处,还希望多和大家交流。
在发送语句到SQL引擎前,FORALL语句告知PL/SQL 引擎批挷定输入集合。尽管FORALL语句包含一个迭代(iteration)模式,它并不一是个FOR循环。其语法为:
  FORALL index IN lower_bound..upper_bound sql_statement;
 

一、如何使用批挷定提高性能(How Do Bulk Binds Improve Performance)
  在PL/SQL 和SQL引擎(engines)中,太多的上下文切换(context switches)会影响性能。这个会发生在当一个循环为集合中的每个元素执行一个单个SQL语句时。而使用批挷定能显著提高性能。下图显示PL/SQL引擎 和SQL引擎之间的context switches:(PL/SQL引擎执行存过语句仅发送SQL语句到SQL引擎,SQL引擎执行语句后返回数据给PL/SQL引擎)
  PL/SQL引擎发送一次SQL语句给SQL引擎,在SQL引擎中则为范围中每个index数字执行一次SQL语句。
  PL/SQL挷定操作包含以下三类:
  in-bind: When a PL/SQL variable or host variable is stored in the database by an INSERT or UPDATE statement.
  out-bind:When a database value is assigned to a PL/SQL variable or a host variable by the RETURNING clause of an INSERT, UPDATE, or DELETE statement.
  define: When a database value is assigned to a PL/SQL variable or a host variable by a SELECT or FETCH statement.
  在SQL语句中,为PL/SQL变量指定值称为挷定(binding),
  DML语句能传递所有集合元素到一个单个操作中,这过程称为批挷定(bulk binding)。
  如果集合有20个元素,批挷定让你用单个操作等效于执行与20个SELECT,INSERT,UPDATE或DELETE语句。这个技术通过减少在PL/SQL和SQL引擎(engines)间的上下文切换来提高性能。批挷定包括:
  1.带INSERT, UPDATE, and DELETE语句的批挷定:在FORALL语句中嵌入SQL语句
  2.带SELECT语句的批挷定:在SELECT语句中用BULK COLLECT 语句代替INTO
  下边的例子分别用FOR和FORALL进行数据插入,以显示用批挷定的对性能的提高:
 

FORALL用法小结 _职场SQL> SET SERVEROUTPUT ON
FORALL用法小结 _职场_02SQL
> CREATE TABLE parts (pnum NUMBER(4), pname CHAR(15));
FORALL用法小结 _职场_03
Table created.
FORALL用法小结 _职场_04SQL
> DECLARE
FORALL用法小结 _FORALL用法小结 _05
2 TYPE NumTab IS TABLE OF parts.pnum%TYPE INDEX BY BINARY_INTEGER;
FORALL用法小结 _FORALL用法小结 _06
3 TYPE NameTab IS TABLE OF parts.pname%TYPE INDEX BY BINARY_INTEGER;
FORALL用法小结 _FORALL用法小结 _07
4 pnums NumTab;
FORALL用法小结 _休闲_08
5 Pnames NameTab;
FORALL用法小结 _职场_09
6 t1 NUMBER;
FORALL用法小结 _职场_10
7 t2 NUMBER;
FORALL用法小结 _休闲_11
8 t3 NUMBER;
FORALL用法小结 _休闲_12
9 BEGIN
FORALL用法小结 _休闲_13
10 FOR i IN 1..500000 LOOP
FORALL用法小结 _FORALL用法小结 _14
11 pnums(i) := i;
FORALL用法小结 _休闲_15
12 pnames(i) := 'Part No.'||to_char(i);
FORALL用法小结 _职场_16
13 END LOOP;
FORALL用法小结 _FORALL用法小结 _17
14 t1 := dbms_utility.get_time;
FORALL用法小结 _FORALL用法小结 _18
15
FORALL用法小结 _休闲_19
16 FOR i IN 1..500000 LOOP
FORALL用法小结 _FORALL用法小结 _20
17 INSERT INTO parts VALUES(pnums(i),pnames(i));
FORALL用法小结 _FORALL用法小结 _21
18 END LOOP;
FORALL用法小结 _FORALL用法小结 _22
19 t2 := dbms_utility.get_time;
FORALL用法小结 _休闲_23
20
FORALL用法小结 _职场_24
21 FORALL i IN 1..500000
FORALL用法小结 _FORALL用法小结 _25
22 INSERT INTO parts VALUES(pnums(i),pnames(i));
FORALL用法小结 _FORALL用法小结 _26
23 t3 := dbms_utility.get_time;
FORALL用法小结 _职场_27
24
FORALL用法小结 _FORALL用法小结 _28
25 dbms_output.put_line('Execution Time (secs)');
FORALL用法小结 _休闲_29
26 dbms_output.put_line('---------------------');
FORALL用法小结 _职场_30
27 dbms_output.put_line('FOR loop: ' || TO_CHAR(t2 - t1));
FORALL用法小结 _休闲_31
28 dbms_output.put_line('FORALL: ' || TO_CHAR(t3 - t2));
FORALL用法小结 _FORALL用法小结 _32
29 END;
FORALL用法小结 _休闲_33SQL
> /
FORALL用法小结 _休闲_34Execution Time (secs)
FORALL用法小结 _休闲_35
---------------------
FORALL用法小结 _休闲_36
FOR loop: 2592
FORALL用法小结 _休闲_37FORALL: 
358
FORALL用法小结 _职场_38PL
/SQL procedure successfully completed

  从而可以看出FORALL语句在性能上有显著提高。
  注释:SQL语句能涉及多个集合,然而,性能提高只适用于下标集合(subscripted collections)
二、FORALL 如何影响回滚(How FORALL Affects Rollbacks)
  在FORALL语句中,如果任何SQL语句执行产生未处理的异常(exception),先前执行的所有数据库改变都会被回滚。然而,如果产生的异常被捕获并处理,则回滚改变到一个隐式的保存点,该保存点在每个SQL语句执行前被标记。之前的改变不会被回滚。例如:
 

FORALL用法小结 _职场_39CREATE TABLE emp2 (deptno NUMBER(2), job VARCHAR2(15));
FORALL用法小结 _休闲_40
INSERT INTO emp2 VALUES(10'Clerk');
FORALL用法小结 _FORALL用法小结 _41
INSERT INTO emp2 VALUES(10'Clerk');
FORALL用法小结 _职场_42
INSERT INTO emp2 VALUES(20'Bookkeeper'); -- 10-char job title
FORALL用法小结 _职场_43
INSERT INTO emp2 VALUES(30'Analyst');
FORALL用法小结 _FORALL用法小结 _44
INSERT INTO emp2 VALUES(30'Analyst');
FORALL用法小结 _FORALL用法小结 _45Comit;
FORALL用法小结 _FORALL用法小结 _46
DECLARE
FORALL用法小结 _休闲_47TYPE NumList 
IS TABLE OF NUMBER;
FORALL用法小结 _FORALL用法小结 _48depts NumList :
= NumList(102030);
FORALL用法小结 _FORALL用法小结 _49
BEGIN
FORALL用法小结 _职场_50FORALL j 
IN depts.FIRST..depts.LAST
FORALL用法小结 _休闲_51
UPDATE emp2 SET job = job || ' (temp)'
FORALL用法小结 _休闲_52
WHERE deptno = depts(j);
FORALL用法小结 _FORALL用法小结 _53
-- raises a "value too large" exception
FORALL用法小结 _FORALL用法小结 _54
EXCEPTION
FORALL用法小结 _休闲_55
WHEN OTHERS THEN
FORALL用法小结 _职场_56
COMMIT;
FORALL用法小结 _FORALL用法小结 _57
END;
FORALL用法小结 _FORALL用法小结 _58
/
FORALL用法小结 _FORALL用法小结 _59PL
/SQL procedure successfully completed
FORALL用法小结 _职场_60SQL
> select * from emp2;
FORALL用法小结 _FORALL用法小结 _61DEPTNO JOB
FORALL用法小结 _职场_62
---------- ---------------
FORALL用法小结 _休闲_63
10 Clerk temp
FORALL用法小结 _职场_64
10 Clerk temp
FORALL用法小结 _FORALL用法小结 _65
20 Bookkeeper
FORALL用法小结 _FORALL用法小结 _66
30 Analyst
FORALL用法小结 _休闲_67
30 Analyst

  上边的例子SQL引擎执行UPDATE语句3次,指定范围内的每个索引号一次。第一个(depts(10))执行成功,但是第二个(depts(20))执行失败(插入值超过了列长),因此,仅仅第二个执行被回滚。
  当执行任何SQL语句引发异常时,FORALL语句中断(halt)。上边的例子中,执行第二个UPDATE语句引发异常, 因此第三个语句不会执行。
三、用%BULK_ROWCOUNT 属性计算FORALL迭代影响行数
  在进行SQL数据操作语句时,SQL引擎打开一个隐式游标(命名为SQL),该游标的标量属性(scalar attribute)有 %FOUND, %ISOPEN, %NOTFOUND, and %ROWCOUNT。
  FORALL语句除具有上边的标量属性外,还有个复合属性(composite attribute):%BULK_ROWCOUNT,该属性具有索引表(index-by table)语法。它的第i个元素存贮SQL语句(INSERT, UPDATE或DELETE)第i个执行的处理行数。如果第i个执行未影响行,%bulk_rowcount (i),返回0。FORALL与%bulk_rowcount属性使用相同下标。例如:
 

FORALL用法小结 _职场_68DECLARE
FORALL用法小结 _职场_69TYPE NumList 
IS TABLE OF NUMBER;
FORALL用法小结 _FORALL用法小结 _70depts NumList :
= NumList(102050);
FORALL用法小结 _FORALL用法小结 _71
BEGIN
FORALL用法小结 _职场_72FORALL j 
IN depts.FIRST..depts.LAST
FORALL用法小结 _休闲_73
UPDATE emp SET sal = sal * 1.10 WHERE deptno = depts(j);
FORALL用法小结 _FORALL用法小结 _74
-- Did the 3rd UPDATE statement affect any rows?
FORALL用法小结 _FORALL用法小结 _75
IF SQL%BULK_ROWCOUNT(3= 0 THEN ...
FORALL用法小结 _职场_76
END;

  %ROWCOUNT 返回SQL语句所有执行处理总的行数
  %FOUND和 %NOTFOUND仅与SQL语句的最后执行有关,但是,可以使用%BULK_ROWCOUNT推断单个执行的值,如%BULK_ROWCOUNT(i)为0时,%FOUND和%NOTFOUND分别是FALSE和TRUE。
四、用%BULK_EXCEPTIONS属性处理FORALL异常
  在执行FORALL语句期间,PL/SQL提供一个处理异常的机制。该机制使批挷定(bulk-bind)操作能保存异常信息并继续执行。方法是在FORALL语句中增加SAVE EXCEPTIONS关键字。语法为:
  FORALL index IN lower_bound..upper_bound SAVE EXCEPTIONS
    {insert_stmt | update_stmt | delete_stmt}
  执行期间引发的所有异常都被保存游标属性 %BULK_EXCEPTIONS中,它存贮一个集合记录,每记录有两个字段:
  %BULK_EXCEPTIONS(i).ERROR_INDEX:存贮在引发异常期间FORALL语句迭代(重复:iteration)
  %BULK_EXCEPTIONS(i).ERROR_CODE:存贮相应的Oracle错误代码
  %BULK_EXCEPTIONS.COUNT存贮异常的数量。(该属性不是%BULK_EXCEPTIONS集合记录的字段)。如果忽略SAVE EXCEPTIONS,当引发异常时,FORALL语句停止执行。此时,SQL%BULK_EXCEPTIONS.COUNT 返回1, 且SQL%BULK_EXCEPTIONS只包含一条记录。如果执行期间无异常 SQL%BULK_EXCEPTIONS.COUNT 返回 0.例子:
 

FORALL用法小结 _FORALL用法小结 _77DECLARE
FORALL用法小结 _职场_78TYPE NumList 
IS TABLE OF NUMBER;
FORALL用法小结 _休闲_79num_tab NumList :
= NumList(10,0,11,12,30,0,20,199,2,0,9,1);
FORALL用法小结 _休闲_80errors 
NUMBER;
FORALL用法小结 _FORALL用法小结 _81dml_errors EXCEPTION;
FORALL用法小结 _休闲_82PRAGMA exception_init(dml_errors, 
-24381);
FORALL用法小结 _休闲_83
BEGIN
FORALL用法小结 _休闲_84FORALL i 
IN num_tab.FIRST..num_tab.LAST SAVE EXCEPTIONS
FORALL用法小结 _职场_85
DELETE FROM emp WHERE sal > 500000/num_tab(i);
FORALL用法小结 _职场_86EXCEPTION
FORALL用法小结 _职场_87
WHEN dml_errors THEN
FORALL用法小结 _FORALL用法小结 _88errors :
= SQL%BULK_EXCEPTIONS.COUNT;
FORALL用法小结 _FORALL用法小结 _89dbms_output.put_line(
'Number of errors is ' || errors);
FORALL用法小结 _FORALL用法小结 _90
FOR i IN 1..errors LOOP
FORALL用法小结 _职场_91dbms_output.put_line(
'Error ' || i || ' occurred during '||
FORALL用法小结 _职场_92
'iteration ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
FORALL用法小结 _FORALL用法小结 _93dbms_output.put_line(
'Oracle error is ' ||
FORALL用法小结 _职场_94SQLERRM(
-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
FORALL用法小结 _职场_95
END LOOP;
FORALL用法小结 _FORALL用法小结 _96
END;

该例子中,当i等于2,6,10时,产生异常ZERO_DIVIDE,完成后SQL%BULK_EXCEPTIONS.COUNT为3,其值为(2,1476), (6,1476)和(10,1476),错误输出如下:
  Number of errors is 3
  Error 1 occurred during iteration 2
  Oracle error is ORA-01476: divisor is equal to zero
  Error 2 occurred during iteration 6
  Oracle error is ORA-01476: divisor is equal to zero
  Error 3 occurred during iteration 10
  Oracle error is ORA-01476: divisor is equal to zero
五、用BULK COLLECT子句取回查询结果至集合中
  在返回到PL/SQL引擎之前,关键字BULK COLLECT告诉SQL引擎批挷定输出集合。该关键字能用于SELECT INTO, FETCH INTO和RETURNING INTO语句中。语法如下:
  ... BULK COLLECT INTO collection_name[, collection_name] ...
 

FORALL用法小结 _职场_97示例1:
FORALL用法小结 _休闲_98
DECLARE
FORALL用法小结 _休闲_99TYPE NumTab 
IS TABLE OF emp.empno%TYPE;
FORALL用法小结 _职场_100TYPE NameTab 
IS TABLE OF emp.ename%TYPE;
FORALL用法小结 _FORALL用法小结 _101enums NumTab; 
-- no need to initialize
FORALL用法小结 _休闲_102
names NameTab;
FORALL用法小结 _休闲_103
BEGIN
FORALL用法小结 _休闲_104
SELECT empno, ename BULK COLLECT INTO enums, names FROM emp;
FORALL用法小结 _FORALL用法小结 _105...
FORALL用法小结 _职场_106
END;
FORALL用法小结 _职场_107示例2:
FORALL用法小结 _职场_108
CREATE TYPE Coords AS OBJECT (x NUMBER, y NUMBER);
FORALL用法小结 _休闲_109
CREATE TABLE grid (num NUMBER, loc Coords);
FORALL用法小结 _FORALL用法小结 _110
INSERT INTO grid VALUES(10, Coords(1,2));
FORALL用法小结 _FORALL用法小结 _111
INSERT INTO grid VALUES(20, Coords(3,4));
FORALL用法小结 _休闲_112
DECLARE
FORALL用法小结 _职场_113TYPE CoordsTab 
IS TABLE OF Coords;
FORALL用法小结 _休闲_114pairs CoordsTab;
FORALL用法小结 _休闲_115
BEGIN
FORALL用法小结 _休闲_116
SELECT loc BULK COLLECT INTO pairs FROM grid;
FORALL用法小结 _职场_117
-- now pairs contains (1,2) and (3,4)
FORALL用法小结 _FORALL用法小结 _118
END;
FORALL用法小结 _职场_119示例3:
FORALL用法小结 _休闲_120
DECLARE
FORALL用法小结 _休闲_121TYPE SalList 
IS TABLE OF emp.sal%TYPE;
FORALL用法小结 _休闲_122sals SalList;
FORALL用法小结 _FORALL用法小结 _123
BEGIN
FORALL用法小结 _职场_124
SELECT sal BULK COLLECT INTO sals FROM emp
FORALL用法小结 _职场_125
WHERE ROWNUM <= 100;
FORALL用法小结 _FORALL用法小结 _126...
FORALL用法小结 _职场_127
END;
FORALL用法小结 _休闲_128示例4:Examples 
of Bulk Fetching from a Cursor
FORALL用法小结 _职场_129
DECLARE
FORALL用法小结 _休闲_130TYPE NameList 
IS TABLE OF emp.ename%TYPE;
FORALL用法小结 _休闲_131TYPE SalList 
IS TABLE OF emp.sal%TYPE;
FORALL用法小结 _休闲_132
CURSOR c1 IS SELECT ename, sal FROM emp WHERE sal > 1000;
FORALL用法小结 _休闲_133names NameList;
FORALL用法小结 _休闲_134sals SalList;
FORALL用法小结 _FORALL用法小结 _135
BEGIN
FORALL用法小结 _职场_136
OPEN c1;
FORALL用法小结 _FORALL用法小结 _137
FETCH c1 BULK COLLECT INTO names, sals; --可返回到一个或多个集合
FORALL用法小结 _休闲_138
END;
FORALL用法小结 _FORALL用法小结 _139示例5:Examples 
of Bulk Fetching from a Cursor
FORALL用法小结 _休闲_140
DECLARE
FORALL用法小结 _职场_141TYPE DeptRecTab 
IS TABLE OF dept%ROWTYPE;
FORALL用法小结 _职场_142dept_recs DeptRecTab;
FORALL用法小结 _FORALL用法小结 _143
CURSOR c1 IS
FORALL用法小结 _FORALL用法小结 _144
SELECT deptno, dname, loc FROM dept WHERE deptno > 10;
FORALL用法小结 _休闲_145
BEGIN
FORALL用法小结 _职场_146
OPEN c1;
FORALL用法小结 _FORALL用法小结 _147
FETCH c1 BULK COLLECT INTO dept_recs; --返回到一个记录(records)集合
FORALL用法小结 _职场_148
END;