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;
/
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