Search code examples
oracle-databaseexceptionplsqlbulkupdate

How does FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT point to the elements of the collection that threw the exceptions?


I have taken this code as an example from this topic:

CREATE OR REPLACE PROCEDURE PROC1 (V_EMP_ID DBMS_SQL.NUMBER_TABLE)
IS
    lv_error_string VARCHAR2(4000);
BEGIN
    FORALL INDX IN V_EMP_ID.FIRST..V_EMP_ID.LAST SAVE EXCEPTIONS
    UPDATE EMPLOYEES 
     ---trying to rasie an exception by using a calculation
    SET SALARY=SALARY * 999999999999
    WHERE ID_E= V_EMP_ID(INDX);

EXCEPTION
    WHEN OTHERS 
    THEN
    FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
    LOOP
        ---Am printing the value of the exception array.
        dbms_output.put_line('exception Raised for record' ||V_EMP_ID(i));           
    END LOOP;
END;
/

Can someone explain how V_EMP_ID(i) points to the actual elements that caused the exception since i is in 1 .. SQL%BULK_EXCEPTIONS.COUNT. Shouldnt V_EMP_ID(i) always be pointing to the first element of the collection in the first iteration?


PS added the below on 12/17

In my code below I'm trying to emulate the MERGE INTO functionality without using MERGE INTO for some reason.

Using collections, I'm inserting records from a temp table to a main table. If that row exists in the main table, we will get a DUP_VAL_ON_INDEX sort of error and the error details get saved using SAVE EXCEPTIONS.
In the EXCEPTION block, I want to update all the main table rows that have errored out and update them with a set of values from the temp table.
Surprisingly, FORALL i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT and temp_data(i) updated the correct rows in the main table for me. I tried non-sequential rows too and it worked well. How is this possible?

declare
TYPE data_tbl IS TABLE OF EMPLOYEE_TEMP%rowtype INDEX BY PLS_INTEGER;
    temp_data data_tbl;
begin       
        select * bulk collect into temp_data from EMPLOYEE_TEMP
        where EVENT_OID='30047767_1' and USERID='SINISDI2';
    
   FORALL i IN 1 .. temp_data.COUNT SAVE EXCEPTIONS
       INSERT INTO EMPLOYEE D
        (D.VPD_KEY,
              D.OID,
              D.EVENT_OID,
              D.PID,
              D.AMOUNT,
              D.MIR,
              D.REPORTING,
              D.MODIFIED_BY_USER,
              D.MODIFIED_ON,
              D.VERSION,
              D.TYPE_OID,
              D.F_ELIGIBLE)
       VALUES ( temp_data(i).VPD_KEY,
              temp_data(i).OID,
              temp_data(i).EVENT_OID,
              temp_data(i).PID,
              temp_data(i).AMOUNT,
              temp_data(i).MIR,
              temp_data(i).REPORTING,
              temp_data(i).USERID,
              SYSDATE,
              0,
              temp_data(i).TYPE_OID,
              temp_data(i).F_ELIGIBLE
              );
  
   EXCEPTION
   WHEN OTHERS
   THEN
      IF SQLCODE = -24381
      THEN
    FORALL i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT 
   UPDATE EMPLOYEE D
     SET      D.AMOUNT=temp_data(i).AMOUNT,
              D.REPORTING=temp_data(i).REPORTING,
              D.MIR=temp_data(i).MIR,
              D.MODIFIED_BY_USER=temp_data(i).USERID,
              D.MODIFIED_ON = temp_data(i).MODIFIED_ON,
              D.VERSION = D.VERSION+1,
              D.F_ELIGIBLE = S.F_ELIGIBLE
       where D.PID = temp_data(i).PID AND D.TYPE_OID = temp_data(i).TYPE_OID;
      ELSE 
         DBMS_OUTPUT.PUT_LINE(SQLERRM(SQLCODE)) ;
      END IF;     
end; 
/  

Solution

  • Quote from the capital 12.4.1.4 Handling FORALL Exceptions After FORALL Statement Completes:

    SQL%BULK_EXCEPTIONS(i).ERROR_INDEX is the number of the DML statement that failed.

    The number of the DML statement is equivalent to the element index in the collection.

    Have a look at following working example:

    create table tab (id, val check (val<=3)) as
        select rownum, rownum from dual connect by level<=3
    /
    declare 
        type idtab is table of int;
        ids idtab;
    begin    
        select id bulk collect into ids
        from tab;
        
        declare
            forallexcp exception;
            pragma exception_init (forallexcp, -24381);
        begin
            forall i in 1..ids.count save exceptions
                update tab set val = val + 1
                where id = ids(i); 
        exception when forallexcp then
            dbms_output.put_line (
                sql%rowcount||' row(s) inserted ('||sql%bulk_exceptions.count||' with error).');    
            for i in 1..sql%bulk_exceptions.count loop dbms_output.put_line (
                'id='||ids(sql%bulk_exceptions(i).error_index)||' sqlerrm='||
                sqlerrm (-(sql%bulk_exceptions(i).error_code)));
            end loop;
        end;
    end;
    /
    

    Result:

    2 row(s) inserted (1 with error).
    id=3 sqlerrm=ORA-02290: check constraint (.) violated