こんにちは! 健史です。
PLSQLで「バルクフェッチ、バルクインサート」のサンプルプログラムを作成しました。
オラクルのサイトを参考にしています。
自分の手を動かして作成することで、理解が深まりました。
プログラム
create or replace PROCEDURE PROC_BULK1 IS --1.バルク サイズ定義 BULK_SIZE CONSTANT PLS_INTEGER := 2000; -- コンスタントでなくても良い --2.カーソル定義 CURSOR CUR1 IS SELECT * FROM 売上履歴 ORDER BY 連番 ; --3.バルク テーブル等定義 -- バルク FETCH用テーブル定義 TYPE IN1TBL IS TABLE OF 売上履歴%ROWTYPE INDEX BY BINARY_INTEGER; IN1REC IN1TBL; -- バルク INSERT用テーブル定義 TYPE OT1TBL IS TABLE OF 売上履歴集計1%ROWTYPE INDEX BY BINARY_INTEGER; OT1REC OT1TBL; -- エラーハンドラ用 ORA-24381: DML配列にエラーがあります。 vBulkErrors PLS_INTEGER := 0; eBulkProcessNotComplete EXCEPTION; PRAGMA EXCEPTION_INIT(eBulkProcessNotComplete, -24381); --4.ワークを定義 入力件数 NUMBER(10); 出力件数 NUMBER(10); WK_TIMESTAMP TIMESTAMP; --5.FUNCTION定義 必要に応じて定義 --6.メイン処理 BEGIN --6-1.初期処理 OPEN CUR1; EXECUTE IMMEDIATE 'TRUNCATE TABLE 売上履歴集計1'; 入力件数 := 0; 出力件数 := 0; SELECT TO_CHAR(SYSTIMESTAMP,'yyyy/mm/dd hh24:mi:ss.ff5') INTO WK_TIMESTAMP FROM DUAL; DBMS_OUTPUT.PUT_LINE('開始時刻:' || WK_TIMESTAMP); --6-2.順次処理 LOOP -- バルク FETCH,INSERT用テーブル初期クリア IN1REC.DELETE; OT1REC.DELETE; -- バルク処理 -- 1).FETCH FETCH CUR1 BULK COLLECT INTO IN1REC LIMIT BULK_SIZE; EXIT WHEN IN1REC.COUNT = 0; -- 2).編集処理 FOR i in 1 .. IN1REC.COUNT LOOP 入力件数 := 入力件数 + 1; OT1REC(i).キー1 := IN1REC(i).キー1; OT1REC(i).キー2 := IN1REC(i).キー2; OT1REC(i).キー3 := IN1REC(i).キー3; OT1REC(i).連番 := IN1REC(i).連番; OT1REC(i).数量 := IN1REC(i).数量; OT1REC(i).単価 := IN1REC(i).単価; OT1REC(i).団体コード := IN1REC(i).団体コード; OT1REC(i).件数 := 0; 出力件数 := 出力件数 + 1; END LOOP; -- 3).INSERT BEGIN FORALL i in 1..OT1REC.COUNT SAVE EXCEPTIONS -- ← エラーが発生しても継続 INSERT INTO 売上履歴集計1 VALUES OT1REC(i); COMMIT; EXCEPTION WHEN eBulkProcessNotComplete THEN vBulkErrors := vBulkErrors + SQL%BULK_EXCEPTIONS.COUNT; -- PROC_BULK_ERROR_HANDLER; END; END LOOP; --7.終了処理 COMMIT; DBMS_OUTPUT.PUT_LINE('入力件数:' || 入力件数); DBMS_OUTPUT.PUT_LINE('出力件数:' || 出力件数); DBMS_OUTPUT.PUT_LINE('エラー数:' || vBulkErrors); SELECT TO_CHAR(SYSTIMESTAMP,'yyyy/mm/dd hh24:mi:ss.ff5') INTO WK_TIMESTAMP FROM DUAL; DBMS_OUTPUT.PUT_LINE('終了時刻:' || WK_TIMESTAMP); CLOSE CUR1; END;
補足
・「バルク FETCH,INSERT用テーブル初期クリア」の理由
-- バルク FETCH,INSERT用テーブル初期クリア IN1REC.DELETE; OT1REC.DELETE;
上記をコメントにして、110件の売上履歴を作成し、バルクサイズを[50]で実行しました。
[DBMS_OUTPUT.PUT_LINE('出力件数:' || 出力件数);]の件数は、110件です。
ですが、テーブル[売上履歴集計1]には、150件のデータが作成されていました。
確認したところ、51-100件処理したときの61-100件のデータが重複して作成されていました。
OT1RECをクリアしないためにデータが残っており、[OT1REC.COUNT]もクリアされず"50"のままになっていることが原因と考えています。
[IN1REC.DELETE;]をコメントしても正常に処理されますが、これはFETCHしたときに[IN1REC.COUNT]が設定されるのでしょう。
わかりやすくするため、このプログラム、及び、以降で記事にするプログラムでは、入出力テーブルをクリアすることにしました。
処理時間の比較
1,500,000件のデータを単純に、FETCH→移送→INSERTするだけです。
バルクサイズを2000件と1件で比較してみました。
1件であれば、バルクを使用せずに処理したことと同じです。
(厳密には、違いがあるかもしれませんが)
・2,000件 0:11
・1件 2:04
開始時刻:25-04-01 12:55:46.479000
入力件数:1500000
出力件数:1500000
エラー数:0
終了時刻:25-04-01 12:55:57.854000
開始時刻:25-04-01 12:56:43.679000
入力件数:1500000
出力件数:1500000
エラー数:0
終了時刻:25-04-01 12:58:47.478000
その他
実際に使用したSQLDEVELOPERの画面を添付します。
コメント