Search code examples
plsqloracle11gbulkinsertparadigmsbulk-collect

Two (or more) DMLs inside one bulk collect operation loop


I have problem with BULK COLLECT logic on Oracle 11g.

The original logic in stored procedure is:

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;

But I want to use BULK COLLECT feature.

I wrote something like that:

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;

Is this good approach for this problem?

What if I have more DML to perform?


Ok. My problem is more complex but I wanted to simplify it and enrich it with nice sample codes. Error OTHERS handing is not part of this problem. Maybe this will be more clear:

How this:

  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;

change to BULK COLLECT and FORALL statements ?


Solution

  • I found solution by using this kind of flow:

    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;
    

    In this flow:

    1. All exceptions ocured in INSERT will be stored in SQL%BULK_EXCEPTIONS collection
    2. Each exception will be logged by DBMS_OUTPUT.PUT_LINE (in real life in log table by AUTONOMOUS TRANSACTION procedure)
    3. Each error index of LT_CUT will be removed by DELETE method on collection.
    4. Only "good" lines will be used in UPDATE because INDICES OF clause allows a bulk operation on a sparse collection by removing the reference to specific elements