PL/SQL

PLSQL バルク処理 コントロールブレイク 構造化

PL/SQL

こんにちは! 健史です。

バルクでのコントロールブレイク処理を構造化してみました。

ですが、複雑になりました。

もっと良い方法があるかもしれません。

もっと良い方法をお持ちの方、考え付いた方は、お問い合わせからご連絡いただけば嬉しいです。

個人的には、バルクでのコントロールブレイク処理は、非構造型の方が良いと思います。

スポンサーリンク

プログラム

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

PL/SQL
スポンサーリンク
- 面白かったらシェアお願いします! -
健史をフォローする
自分で改善

コメント