BULK Exception
BULK Exception
BULK Exception
We all know how to use BULK COLLECT and FORALL. Easy, right?
But, so many errors can occur when you are trying to change rows in a table.
Constraint violations, values too large for columns, and many more. Now add
to that the fact that with FORALL you are executing that DML statement many
times over. Managing errors with FORALL is a tricky but important thing to do!
Before diving into the error-related features of FORALL, let’s review some
important points about transactions, errors, and exceptions in the world of
PL/SQL.
Each SQL statement is atomic (‘all or nothing’). In other words, if your update
statement finds 100 rows to change, and as it is changing the 100th of them, if
it hits an error, the changes to all 100 rows are reversed.
What does this mean for FORALL? That, by default, the first time the SQL
engine encounters an error processing the DML statement passed to it from
FORALL, it stops and passes the error back to the PL/SQL engine. No further
processing is done, but also any statements completed successfully by the
FORALL are still waiting to be committed or rolled back.
DECLARE
TYPE bulkcollect_t IS TABLE OF VARCHAR2(10 CHAR);
l_string bulkcollect_t := bulkcollect_t ('A', 'AA', 'AAA');
BEGIN
FORALL indx IN 1 .. l_string.COUNT
INSERT INTO bulkcollect_test (c)
VALUES (l_string (indx));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Rows inserted : ' || SQL%ROWCOUNT);
DBMS_OUTPUT.put_line (SQLERRM);
ROLLBACK;
END;
/
DECLARE
TYPE bulkcollect_t IS TABLE OF VARCHAR2(10 CHAR);
l_string bulkcollect_t := bulkcollect_t ('AAA', 'AA', 'A');
BEGIN
FORALL indx IN 1 .. l_string.COUNT
INSERT INTO bulkcollect_test (c)
VALUES (l_string (indx));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Rows inserted : ' || SQL%ROWCOUNT);
DBMS_OUTPUT.put_line (SQLERRM);
ROLLBACK;
END;
/
DECLARE
TYPE bulkcollect_t IS TABLE OF VARCHAR2(10 CHAR);
l_string bulkcollect_t := bulkcollect_t ('A', 'AAA', 'AA');
BEGIN
FORALL indx IN 1 .. l_string.COUNT
INSERT INTO bulkcollect_test (c)
VALUES (l_string (indx));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Rows inserted : ' || SQL%ROWCOUNT);
DBMS_OUTPUT.put_line (SQLERRM);
ROLLBACK;
END;
/
Copy
The first block updated 2 rows, the second block updated 0 rows, and the third
block updated 1 row.
With FORALL, you are choosing to execute the same statement, many times
over. It could be that you do want to stop your FORALL as soon as any
statement fails. In which case, you are done. If, however, you want to keep on
going, even if there is an SQL error for a particular set of bind variable values,
you need to take advantage of the SAVE EXCEPTIONS clause.
Add the SAVE EXCEPTIONS clause to your FORALL statement when you want
the PL/SQL runtime engine to execute all DML statements generated by the
FORALL, even if one or more than fail with an error.
In the above code, I had some errors. You can’t insert more than 2 characters.
But without SAVE EXCEPTIONS we never get past the element where we have
more than 2 characters. Only 1 row was inserted when we executed the last
block.
DECLARE
TYPE bulkcollect_t IS TABLE OF VARCHAR2(10 CHAR);
l_string bulkcollect_t := bulkcollect_t ('A', 'AAA', 'AA');
BEGIN
FORALL indx IN 1 .. l_string.COUNT SAVE EXCEPTIONS
INSERT INTO bulkcollect_test (c)
VALUES (l_string (indx));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Rows inserted : ' || SQL%ROWCOUNT);
DBMS_OUTPUT.put_line (SQLERRM);
ROLLBACK;
END;
/
Copy
Now, 2 rows have been inserted.
You can—and should—trap that error in the exception section and then
iterate through the contents of SQL%BULK_EXCEPTIONS to find out which
errors have occurred. You can then write error information to a log table
and/or attempt recovery of the DML statement.
Let’s go back to the same block used to show the effects of SAVE
EXCEPTIONS, but now also take advantage of SQL%BULK_EXCEPTIONS.
DECLARE
TYPE bulkcollect_t IS TABLE OF VARCHAR2(10 CHAR);
l_string bulkcollect_t := bulkcollect_t ('A', 'AAA', 'AA');
l_error_count NUMBER;
BEGIN
FORALL indx IN 1 .. l_string.COUNT SAVE EXCEPTIONS
INSERT INTO bulkcollect_test (c)
VALUES (l_string (indx));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Rows inserted : ' || SQL%ROWCOUNT);
l_error_count := SQL%BULK_EXCEPTIONS.count;
DBMS_OUTPUT.put_line('Number of failures: ' || l_error_count);
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.put_line (
'Error '
|| indx
|| ' occurred on index '
|| SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX
|| ' attempting to update name to "'
|| l_string (
SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX)
|| '"');
DBMS_OUTPUT.put_line (
'Oracle error is : '
|| SQLERRM(-SQL%BULK_EXCEPTIONS(indx).ERROR_CODE));
END LOOP;
ROLLBACK;
END;
/