3

Oracle 11g の BULK COLLECT ロジックに問題があります。

ストアド プロシージャの元のロジックは次のとおりです。

PROCEDURE FOO(IN_FOO IN VARCHAR2) IS
BEGIN
  FOR CUR IN (SELECT COL1,COL2,COL3 FROM SOME_TABLE) LOOP
    INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (CUR.COL1,CUR.COL2,CUR.COL3);
    UPDATE THIRD_TABLE T SET T.C_SUM = CUR.COL2 + CUR.COL3 WHERE T.C_ID = CUR.COL1);
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERROR || ': ' || SQLERRM);
END FOO;

しかし、私は機能を使いたいBULK COLLECTです。

私はそのようなことを書きました:

PROCEDURE FOO_FAST(IN_FOO IN VARCHAR2) IS
  CURSOR CUR IS SELECT COL1,COL2,COL3 FROM SOME_TABLE;
  TYPE RT_CUR IS TABLE OF CUR%ROWTYPE;
  LT_CUR RT_CUR;
  DML_EXCEPTION EXCEPTION;
  PRAGMA EXCEPTION_INIT(DML_EXCEPTION, -24381);
BEGIN
  OPEN CUR;
  LOOP
    FETCH CUR BULK COLLECT INTO LT_CUR LIMIT 1000;
    EXIT WHEN LT_CUR.COUNT = 0;
    BEGIN
      FORALL I IN 1 .. LT_CUR.COUNT 
        INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (LT_CUR(I).COL1,LT_CUR(I).COL2,LT_CUR(I).COL3);
      FORALL I IN 1 .. LT_CUR.COUNT 
        UPDATE THIRD_TABLE T SET T.C_SUM = LT_CUR(I).COL2 + LT_CUR(I).COL3 WHERE T.C_ID = LT_CUR(I).COL1);
    EXCEPTION
      WHEN DML_EXCEPTION THEN
        FORALL I IN 1 .. SQL%BULK_EXCEPTIONS(1).ERROR_INDEX-1
          UPDATE THIRD_TABLE T SET T.C_SUM = LT_CUR(I).COL2 + LT_CUR(I).COL3 WHERE T.C_ID = LT_CUR(I).COL1);
        DBMS_OUTPUT.PUT_LINE(SQLERRM(-SQL%BULK_EXCEPTIONS(1).ERROR_CODE));
        RETURN;
    END;
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERROR || ': ' || SQLERRM);
END FOO_FAST;

この問題に対するこの良いアプローチはありますか?

実行する DML がさらにある場合はどうすればよいですか?


Ok。私の問題はもっと複雑ですが、それを単純化し、素敵なサンプル コードで充実させたいと考えました。エラーOTHERS処理は、この問題の一部ではありません。多分これはより明確になるでしょう:

これはどう:

  FOR CUR IN (SELECT COL1,COL2,COL3 FROM SOME_TABLE) LOOP
    INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (CUR.COL1,CUR.COL2,CUR.COL3);
    UPDATE THIRD_TABLE T SET T.C_SUM = CUR.COL2 + CUR.COL3 WHERE T.C_ID = CUR.COL1);
  END LOOP;

BULK COLLECTandFORALLステートメントに変更しますか?

4

3 に答える 3

2

何かが「良いアプローチ」であるかどうかは非常に主観的です-それはあなたが比較しようとしているものに依存します。

に対するクエリに述語がないと仮定するとsome_table、ループを実行するよりも、セットで作業する方が(コードがはるかに少ないことに加えて)ほぼ確実に効率的です。

PROCEDURE FOO(IN_FOO IN VARCHAR2) IS
BEGIN
  INSERT INTO other_table( c1, c2, c3 )
    SELECT col1, col2, col3
      FROM some_table;

  UPDATE third_table tt
     SET tt.c_sum = (SELECT st.col2 + st.col3
                       FROM some_table st
                      WHERE tt.c_id = st.col1)
   WHERE EXISTS( SELECT 1
                   FROM some_table st
                  WHERE tt.c_id = st.col1);
END;

一般に、WHEN OTHERS例外ハンドラーは悪い考えです。DBMS_OUTPUT例外をキャッチして、呼び出し元がエラーが発生したことを認識できない場所、エラースタックが失われた場所、および呼び出し元のアプリケーションが書き込まれるデータ用のバッファーを割り当てたという保証がない場所にのみ、例外を書き込もうとします。 toは発生するのを待っているバグです。システムにこの種のコードがある場合、どこかで例外が発生して飲み込まれ、コードの後半のビットが予期しない方法で失敗するため、必然的にバグの再現が困難になります。

于 2013-01-29T15:34:52.267 に答える
2

エラー管理に関する元の手順に問題があり、ロジックを一括処理に変換するのが難しくなっています。

基本的に、最初の手順のロジックは次のとおりです。2 つのステートメントをループで実行し、最初にエラーが発生したとき、またはカーソルの最後で正常に終了します。

これは正しいトランザクション ロジックではありません。2 つのステートメントが連携して機能し、2 番目のステートメントが失敗した場合、最初のステートメントはロールバックされません。

おそらくやりたいことは次のとおりです。2 つのステートメントをループで実行します。エラーが発生した場合は、情報をログに記録し、変更を元に戻します。正常に終了しない場合は、元に戻します。PL/SQL では、変更を元に戻すのは非常に簡単です。エラーを伝播させるだけで済みます。

PROCEDURE FOO(IN_FOO IN VARCHAR2) IS
BEGIN
  FOR CUR IN (SELECT COL1,COL2,COL3 FROM SOME_TABLE) LOOP
    BEGIN
       INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (CUR.COL1,CUR.COL2,CUR.COL3);
       UPDATE THIRD_TABLE T SET T.C_SUM = CUR.COL2 + CUR.COL3 
        WHERE T.C_ID = CUR.COL1;
    EXCEPTION
       WHEN OTHERS THEN
          dbms_output.put_line(cur.col1/*...*/); -- log **useful** debug info
          RAISE;-- very important for transactional logic
    END;
  END LOOP;
END;

ところで、これDBMS_OUTPUTは最適なログ ツールではありません。ログ テーブルと自律型トランザクション プロシージャを作成して、関連するエラー メッセージと識別子を挿入することをお勧めします。

バルク ロジックを使用して上記の手順を変換する場合は、Justin Caveが説明した方法(単一の DML ステートメント) を使用することをお勧めします。一括配列を使用する場合、SAVE EXCEPTIONS個々の例外をログに記録する場合は、句を使用する必要があります。エラーを再度発生させることを忘れないでください。これはうまくいくはずです:

PROCEDURE foo_fast(in_foo IN VARCHAR2) IS
   CURSOR cur IS
      SELECT col1, col2, col3 FROM some_table;
   TYPE rt_cur IS TABLE OF cur%ROWTYPE;
   lt_cur rt_cur;
   dml_exception EXCEPTION;
   PRAGMA EXCEPTION_INIT(dml_exception, -24381);
BEGIN
   OPEN cur;
   LOOP
      FETCH cur BULK COLLECT
         INTO lt_cur LIMIT 1000;
      EXIT WHEN lt_cur.COUNT = 0;
      BEGIN
         FORALL i IN 1 .. lt_cur.COUNT SAVE EXCEPTIONS -- important clause
            INSERT INTO other_table (c1, c2, c3) 
               VALUES (lt_cur(i).col1, lt_cur(i).col2, lt_cur(i).col3);
         FORALL i IN 1 .. lt_cur.COUNT SAVE EXCEPTIONS -- 
            UPDATE third_table t SET t.c_sum = lt_cur(i).col2 + lt_cur(i).col3 
             WHERE t.c_id = lt_cur(i).col1;
      EXCEPTION
         WHEN dml_exception THEN
            FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP
               dbms_output.put_line('error '||i||':'||
                      SQL%BULK_EXCEPTIONS(i).error_code);
               dbms_output.put_line('col1='|| 
                      lt_cur(SQL%BULK_EXCEPTIONS(i).error_index).col1);-- 11g+
            END LOOP;
         raise_application_error(-20001, 'error in bulk processing');
      END;
   END LOOP;
END foo_fast;
于 2013-01-29T15:56:45.470 に答える
1

この種のフローを使用して解決策を見つけました:

PROCEDURE FOO_FAST(IN_FOO IN VARCHAR2) IS
  CURSOR CUR IS SELECT COL1,COL2,COL3 FROM SOME_TABLE;
  TYPE RT_CUR IS TABLE OF CUR%ROWTYPE;
  LT_CUR RT_CUR;
  DML_EXCEPTION EXCEPTION;
  PRAGMA EXCEPTION_INIT(DML_EXCEPTION, -24381);
BEGIN
  OPEN CUR;
  LOOP
    FETCH CUR BULK COLLECT INTO LT_CUR LIMIT 1000;
    EXIT WHEN LT_CUR.COUNT = 0;
    BEGIN
      FORALL I IN 1 .. LT_CUR.COUNT SAVE EXCEPTIONS
        INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (LT_CUR(I).COL1,LT_CUR(I).COL2,LT_CUR(I).COL3);
    EXCEPTION
      WHEN DML_EXCEPTION THEN
        FOR I IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
          DBMS_OUTPUT.PUT_LINE(SQLERRM(-SQL%BULK_EXCEPTIONS(1).ERROR_CODE));
          LT_CUR.DELETE(SQL%BULK_EXCEPTIONS(1).ERROR_INDEX);
    END;
    FORALL I IN INDICES OF LT_CUR 
        UPDATE THIRD_TABLE T SET T.C_SUM = LT_CUR(I).COL2 + LT_CUR(I).COL3 WHERE T.C_ID = LT_CUR(I).COL1);
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERROR || ': ' || SQLERRM);
END FOO_FAST;

このフローでは:

  1. 発生したすべての例外はコレクションINSERTに保存されますSQL%BULK_EXCEPTIONS
  2. 各例外はによってログに記録されますDBMS_OUTPUT.PUT_LINE(実際には、AUTONOMOUS TRANSACTIONプロシージャごとにログテーブルに記録されます)
  3. の各エラー インデックスは、コレクションのメソッドLT_CUTによって削除されます。DELETE
  4. 句では、特定の要素への参照を削除することにより、スパース コレクションに対する一括操作が許可されるUPDATEため、「適切な」行のみが使用されます。INDICES OF
于 2013-02-19T16:27:25.413 に答える