【パターン1】
DECLARE TYPE empno_tbl_type IS TABLE OF EMP.EMPNO%TYPE INDEX BY BINARY_INTEGER; empno_tbl empno_tbl_type; BEGIN SELECT EMPNO BULK COLLECT INTO empno_tbl FROM EMP; IF empno_tbl.COUNT > 0 THEN FOR i IN empno_tbl.FIRST..empno_tbl.LAST LOOP UPDATE EMP SET SAL = SAL * 1.05 WHERE EMPNO = empno_tbl( i ); END LOOP; END IF; END; /
【パターン2】
DECLARE TYPE emp_tbl_type IS TABLE OF EMP%ROWTYPE INDEX BY BINARY_INTEGER; emp_tbl emp_tbl_type; BEGIN SELECT * BULK COLLECT INTO emp_tbl FROM EMP; IF emp_tbl.COUNT > 0 THEN FOR i IN emp_tbl.FIRST..emp_tbl.LAST LOOP UPDATE EMP SET SAL = SAL * 1.05 WHERE EMPNO = emp_tbl( i ).EMPNO; END LOOP; END IF; END; /
この時、SELECT文からバルク・バインドされる値はコレクション変数で受け取る必要があります。
コレクション変数は上記の例のように列ごとのコレクション変数(パターン1)や、レコード型のコレクション変数(パターン2)が利用できます。
また、以下のようにカーソルを利用することもでます(パターン3)。(※)
【パターン3】
DECLARE CURSOR emp_cur IS SELECT * FROM EMP; TYPE emp_tbl_type IS TABLE OF emp_cur%ROWTYPE INDEX BY BINARY_INTEGER; emp_tbl emp_tbl_type; BEGIN OPEN emp_cur; FETCH emp_cur BULK COLLECT INTO emp_tbl; CLOSE emp_cur; IF emp_tbl.COUNT > 0 THEN FOR i IN emp_tbl.FIRST..emp_tbl.LAST LOOP UPDATE EMP SET SAL = SAL * 1.05 WHERE EMPNO = emp_tbl( i ).EMPNO; END LOOP; END IF; END; /
なお、カーソルを利用する場合、取得するレコード数が多すぎるようであれば、limit句を指定して一定レコード数単位でフェッチしたほうが効率がよくなります。
FETCH emp_cur BULK COLLECT INTO emp_tbl LIMIT 100;
※カーソルを利用する場合、コレクション変数をカーソルのレコード型にすると、Oracleのバージョン(9.2.0.1など)によってはコンパイル時に
"PLS-00597: INTOリスト内の式 emp_tblの型が正しくありません。"というエラーが発生することがあります。
BULK COLLECT句は"SELECT ~ INTO ~"やカーソルのほか、"RETURNING ~ INTO ~"でも利用することができ、大量のレコードを扱う様々な場面でパフォーマンスの向上に有効な手段です。
ただし、上記の例のように取得後の処理が単純であれば、副問い合わせを使うなどして単一のDMLで操作することも可能であり、当然そうした方がさらにパフォーマンスは向上します。
また、取得するレコード数が多すぎてLIMIT句を使わなければならない場合や、取得したコレクションの処理中にブレイク処理などが必要な場合には、コレクションの制御が複雑になり、バグの温床ともなりかねません。
故に、大量のレコードを取得する場合はBULK COLLECT句を使うというふうに単純に判断するのは必ずしも賢明とは言えません。
処理内容、処理件数などをよく検討した上で採用した方がいいでしょう。
【関連エントリ】
[Oracle] BULK COLLECT句を使ったバルクバインドによる検索処理の高速化 2005/05/20
[Oracle] FORALL文を使ったバルクバインドによる更新処理の高速化 2005/05/25