こんにちは! 健史です。
バルクでのコントロールブレイク処理を構造化してみました。
ですが、複雑になりました。
もっと良い方法があるかもしれません。
もっと良い方法をお持ちの方、考え付いた方は、お問い合わせからご連絡いただけば嬉しいです。
個人的には、バルクでのコントロールブレイク処理は、非構造型の方が良いと思います。
プログラム
create or replace PROCEDURE PROC_BULK3 AS
--1.バルク サイズ定義
BULK_SIZE CONSTANT PLS_INTEGER := 2000; -- コンスタントでなくても良い
--2.カーソル定義
CURSOR CUR1 IS
SELECT * FROM 売上履歴
ORDER BY キー1,キー2,連番 ;
--3.バルク テーブル等定義
-- バルク FETCH用テーブル定義
TYPE IN1TBL IS TABLE OF 売上履歴%ROWTYPE INDEX BY BINARY_INTEGER;
IN1REC IN1TBL;
-- バルク INSERT用テーブル定義 WK_KEY1
TYPE OT1TBL IS TABLE OF 売上履歴集計1%ROWTYPE INDEX BY BINARY_INTEGER;
OT1REC OT1TBL;
-- バルク INSERT用テーブル定義 WK_KEY2
TYPE OT2TBL IS TABLE OF 売上履歴集計2%ROWTYPE INDEX BY BINARY_INTEGER;
OT2REC OT2TBL;
-- バルク エラーハンドラ用 ORA-24381: DML配列にエラーがあります。
vBulkErrors PLS_INTEGER := 0;
eBulkProcessNotComplete EXCEPTION;
PRAGMA EXCEPTION_INIT(eBulkProcessNotComplete, -24381);
--4.ワークを定義
入力件数 NUMBER(10);
出力件数1 NUMBER(10);
出力件数2 NUMBER(10);
WK_TIMESTAMP TIMESTAMP;
IXI1 NUMBER(6);
IXO1 NUMBER(6);
IXO2 NUMBER(6);
SV_KEY1 VARCHAR2(100);
SV_KEY2 VARCHAR2(100);
WK_CNT1 NUMBER(10);
WK_CNT2 NUMBER(10);
-- バルク処理でコントロールブレイクを行うための制御スイッチ
FLG_SKIP1 NUMBER(1);
FLG_SKIP2 NUMBER(1);
DUMMY CHAR(1);
--5.FUNCTION定義 必要に応じて定義
--5.INSERT1
FUNCTION FUNC_INSERT1
RETURN VARCHAR2 IS
BEGIN
FORALL i in 1..OT1REC.COUNT SAVE EXCEPTIONS -- ← エラーが発生しても継続
INSERT INTO 売上履歴集計1 VALUES OT1REC(i);
COMMIT;
RETURN(NULL);
-- バルク PROC_BULK_ERROR_HANDLER;
EXCEPTION
WHEN eBulkProcessNotComplete THEN
vBulkErrors := vBulkErrors + SQL%BULK_EXCEPTIONS.COUNT;
RETURN(NULL);
END;
--5.INSERT2
FUNCTION FUNC_INSERT2
RETURN VARCHAR2 IS
BEGIN
FORALL i in 1..OT2REC.COUNT SAVE EXCEPTIONS -- ← エラーが発生しても継続
INSERT INTO 売上履歴集計2 VALUES OT2REC(i);
COMMIT;
RETURN(NULL);
-- バルク PROC_BULK_ERROR_HANDLER;
EXCEPTION
WHEN eBulkProcessNotComplete THEN
vBulkErrors := vBulkErrors + SQL%BULK_EXCEPTIONS.COUNT;
RETURN(NULL);
END;
--5.OUT-RECORD-SET1
FUNCTION FUNC_OT_SET1
RETURN VARCHAR2 IS
BEGIN
OT1REC(IXO1).件数 := WK_CNT1 ;
出力件数1 := 出力件数1 + 1;
RETURN(NULL);
END;
--5.OUT1-RECORD-SET2
FUNCTION FUNC_OT_SET2
RETURN VARCHAR2 IS
BEGIN
OT2REC(IXO2).件数 := WK_CNT2 ;
出力件数2 := 出力件数2 + 1;
RETURN(NULL);
END;
--6.メイン処理
BEGIN
--6-1.初期処理
OPEN CUR1;
EXECUTE IMMEDIATE 'TRUNCATE TABLE 売上履歴集計1';
EXECUTE IMMEDIATE 'TRUNCATE TABLE 売上履歴集計2';
入力件数 := 0;
出力件数1 := 0;
出力件数2 := 0;
SELECT TO_CHAR(SYSTIMESTAMP,'yyyy/mm/dd hh24:mi:ss.ff5') INTO WK_TIMESTAMP FROM DUAL;
DBMS_OUTPUT.PUT_LINE('開始時刻:' || WK_TIMESTAMP);
-- バルク FETCH,INSERT用テーブル初期クリア
IN1REC.DELETE;
OT1REC.DELETE;
OT2REC.DELETE;
-- バルク FETCH
FETCH CUR1 BULK COLLECT INTO IN1REC LIMIT BULK_SIZE;
IXI1 := 1 ;
IXO1 := 0 ;
IXO2 := 0 ;
FLG_SKIP1 := 0;
FLG_SKIP2 := 0;
--6-2.コントロールブレイク
WHILE IN1REC.COUNT != 0 LOOP
--6-2-1.初期処理1
IF FLG_SKIP1 != 2 THEN
SV_KEY1 := IN1REC(IXI1).キー1 ;
WK_CNT1 := 0;
IXO1 := IXO1 + 1 ;
OT1REC(IXO1).キー1 := IN1REC(IXI1).キー1;
OT1REC(IXO1).キー2 := IN1REC(IXI1).キー2;
OT1REC(IXO1).キー3 := IN1REC(IXI1).キー3;
OT1REC(IXO1).連番 := IN1REC(IXI1).連番;
OT1REC(IXO1).数量 := IN1REC(IXI1).数量;
OT1REC(IXO1).単価 := IN1REC(IXI1).単価;
OT1REC(IXO1).団体コード := IN1REC(IXI1).団体コード;
OT1REC(IXO1).件数 := 0;
END IF;
--6-2-1.初期処理2
WHILE IXI1 <= IN1REC.COUNT AND SV_KEY1 = IN1REC(IXI1).キー1 LOOP
IF FLG_SKIP2 != 2 THEN
SV_KEY2 := IN1REC(IXI1).キー2 ;
WK_CNT2 := 0;
IXO2 := IXO2 + 1 ;
OT2REC(IXO2).キー1 := IN1REC(IXI1).キー1;
OT2REC(IXO2).キー2 := IN1REC(IXI1).キー2;
OT2REC(IXO2).キー3 := IN1REC(IXI1).キー3;
OT2REC(IXO2).連番 := IN1REC(IXI1).連番;
OT2REC(IXO2).数量 := IN1REC(IXI1).数量;
OT2REC(IXO2).単価 := IN1REC(IXI1).単価;
OT2REC(IXO2).団体コード := IN1REC(IXI1).団体コード;
OT2REC(IXO2).件数 := 0;
END IF;
--6-2-2.集計処理
WHILE IXI1 <= IN1REC.COUNT AND IN1REC(IXI1).キー1 = SV_KEY1 AND IN1REC(IXI1).キー2 = SV_KEY2 LOOP
入力件数 := 入力件数 + 1;
WK_CNT1 := WK_CNT1 + 1;
WK_CNT2 := WK_CNT2 + 1;
IXI1 := IXI1 + 1 ;
END LOOP;
--6-2-3.後処理2
-- FETCHしたデータが終わったとき
IF IXI1 > IN1REC.COUNT THEN
-- 次のバルクFETCH
IN1REC.DELETE;
FETCH CUR1 BULK COLLECT INTO IN1REC LIMIT BULK_SIZE;
IXI1 := 1 ;
-- FETCHした次のデータが終わり、または、FETCHした次のデータがあり、ブレイクしていたとき
IF IN1REC.COUNT = 0 OR (IN1REC(IXI1).キー1 != SV_KEY1 OR IN1REC(IXI1).キー2 != SV_KEY2) THEN
-- 1).集計データをINSERT用テーブルにセット
DUMMY := FUNC_OT_SET2();
-- 2).INSERT
DUMMY := FUNC_INSERT2();
-- 3).INSERT用テーブル初期クリア
OT2REC.DELETE;
IXO2 := 0 ;
FLG_SKIP2 := 1; --FETCHした次のデータは、キーが異なりブレイク処理したので初期処理から
ELSE
FLG_SKIP2 := 2; --FETCHした次のデータは、キーが同じでブレイクしていないので初期処理はスキップ
END IF;
-- FETCHデータがまだある状態でブレイクしたときは、集計データをINSERT用テーブルにセット
ELSE
DUMMY := FUNC_OT_SET2();
FLG_SKIP2 := 9; --FETCHデータがあり、ブレイクしたので初期処理から
END IF;
END LOOP;
--6-2-3.後処理1
-- ***最小のブレイク処理でFETCHした次のデータが終わり***ここだけ別の処理の変数を使用しているので注意
IF FLG_SKIP2 < 9 THEN
-- FETCHした次のデータがあり、ブレイクしていたとき
IF IN1REC.COUNT = 0 OR SV_KEY1 != IN1REC(IXI1).キー1 THEN
-- 1).集計データをINSERT用テーブルにセット
DUMMY := FUNC_OT_SET1();
-- 2).INSERT
DUMMY := FUNC_INSERT1();
-- 3).INSERT用テーブル初期クリア
OT1REC.DELETE;
IXO1 := 0 ;
FLG_SKIP1 := 1; --FETCHした次のデータは、キーが異なりブレイク処理したので初期処理から
ELSE
FLG_SKIP1 := 2; --FETCHした次のデータは、キーが同じでブレイクしていないので初期処理はスキップ
END IF;
ELSE
DUMMY := FUNC_OT_SET1();
FLG_SKIP1 := 9; --FETCHデータがあり、ブレイクしたので初期処理から
END IF;
END LOOP;
--7.終了処理
COMMIT;
DBMS_OUTPUT.PUT_LINE('入力件数 :=' || 入力件数);
DBMS_OUTPUT.PUT_LINE('出力件数1 :=' || 出力件数1);
DBMS_OUTPUT.PUT_LINE('出力件数2 :=' || 出力件数2) ;
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 PROC_BULK3;
処理時間の比較
・2,000件 0:09
・ 1件 0:37
開始時刻:25-05-25 08:09:27.761000
入力件数 :=1500000
出力件数1 :=15000
出力件数2 :=150000
エラー数:0
終了時刻:25-05-25 08:09:36.048000
開始時刻:25-05-25 08:12:48.290000
入力件数 :=1500000
出力件数1 :=15000
出力件数2 :=150000
エラー数:0
終了時刻:25-05-25 08:13:25.363000
コメント