I have a big Oracle script with thousands of package call inside a BEGIN - END;
Is there a way to ignore the lines that causes error and continue executing the next lines? Some sort of "On Error Resume Next" in vb.
If you have only one BEGIN END section, then you can use EXCEPTION WHEN OTHERS THEN NULL.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
exception when others then null;
end;
SQL> /
PL/SQL procedure successfully completed.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
--exception when others then null;
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 6
SQL>
The whole concept of "ignore errors" is a bug, and a lie if any errors occur. That is not to say you cannot trap errors and continue processing, just that you MUST handle the errors. For example, assume the use case: "Data has been loaded into a stage table from multiple .csv files. Now load into the tables A and Table B according to ....".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, col_a2);
insert into table_b (col1, col2)
values (rec.col_b1, col_b2);
exception
when others then null;
end;
end loop;
process_message := 'Load Tables A,B Complete';
end ;
Now suppose a user created the a .csv file entered "n/a" in numeric columns where there was no value or the value was unknown. The result of this all too common occurrence is all such rows were not loaded, but you have no way to know that until the user complains their data was not loaded even though you told them it was. Further you have no way of determining the problem.
A much better approach is to "capture and report".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
load_error_occurred boolean := False;
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, rec.col_a2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
begin
insert into table_b (col1, col2)
values (rec.col_b1, rec.col_b2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
end loop;
if load_error_occurred then
process_message := 'Load Tables A,B Complete: Error(s) Detected';
else
process_message := 'Load Tables A,B Complete: Successful No Error(s)';
end if;
end Load_Tables_A_B_from_Stage ;
Now you have informed the user of the actual status, and where you are contacted you can readily identify the issue.
User here is used in the most general sense. It could mean a calling routine instead of an individual. Point is you do not have to terminate your process due to errors but DO NOT ignore them.
I don't think there is any magic one-liner that will solve this.
As others have, use a editor to automate the wrapping of each call within a BEGIN-EXCEPTION-END block might be quicker/easier.
But, if feel a little adventurous, or try this strategy:
Let's assume you have this:
BEGIN
proc1;
proc2;
proc3;
.
.
.
proc1000;
END;
You could try this (untested, uncompiled but might give you an idea of what to try):
DECLARE
l_progress NUMBER := 0;
l_proc_no NUMBER := 0;
e_proc_err EXCEPTION;
-- A 'runner' procedure than manegrs the counters and runs/skips dpending on these vals
PROCEDURE run_proc ( pname IN VARCHAR2 ) IS
BEGIN
l_proc_no := l_proc_no + 1;
IF l_proc_no >= l_progress
THEN
-- log 'Running pname'
EXECUTE IMMEDIATE 'BEGIN ' || pname || '; END;' ;
l_progress := l_progress + 1;
ELSE
-- log 'Skipping pname'
END IF;
EXCEPTION
WHEN OTHERS THEN
-- log 'Error in pname'
l_progress := l_progress + 1;
RAISE e_proc_err;
END;
BEGIN
l_progress := 0;
<<start>>
l_proc_no := 0;
run_proc ( 'proc1' );
run_proc ( 'proc2' );
run_proc ( 'proc3' );
.
.
run_proc ( 'proc1000' );
EXCEPTION
WHEN e_proc_err THEN
GOTO start;
WHEN OTHERS THEN
RAISE;
END;
The idea here is to add a 'runner' procedure to execute each procedure dynamically and log the run, skip, error.
We maintain a global count of the current process number (l_proc_no) and overall count of steps executed (l_progress).
When an error occurs we log it, raise it and let it fall into the outer blocks EXCEPTION handler where it will restart via an (evil) GOTO.
The GOTO is placed such that the overall execution count is unchanged but the process number is reset to 0.
Now when the run_proc is called it sees that l_progress is greater than l_proc_no, and skips it.
Why is this better than simply wrapping a BEGIN EXCEPTION END around each call?
It might not be, but you make a smaller change to each line of code, and you standardise the logging around each call more neatly.
The danger is a potential infinite loop which is why I specify e_proc_err to denote errors within the called procedures. But it might need tweaking to make it robust.
Related
How do I record the oracle error in a pl/sql script? I have been to the oracle error handling documentation and I see the built in exceptions, but what if I do not know what the exception is? How can I log this in an exception block?
I want to do something like the below.
exception
when others then DBMS_OUTPUT.PUT_LINE(the error)
It's easier to create error logging database trigger with conditional logging, for example my actual error_logging trigger: https://github.com/xtender/xt_scripts/blob/master/error_logging/on_database.sql
create table ERROR_LOG
(
id NUMBER,
username VARCHAR2(30),
errcode INTEGER,
seq INTEGER,
tmstmp TIMESTAMP(6),
msg VARCHAR2(4000),
sql_text CLOB
)
/
create sequence err_seq
/
create or replace trigger trg_error_logging
after servererror
on database
disable
declare
v_id number := err_seq.nextval();
v_tmstmp timestamp:= systimestamp;
n int;
sql_text dbms_standard.ora_name_list_t;
v_sql_text clob;
begin
-- only if plsql_debug is set to TRUE:
for r in (select * from v$parameter p where p.name='plsql_debug' and upper(p.value)='TRUE') loop
v_sql_text:=null;
n := ora_sql_txt(sql_text);
for i in 1..n loop
v_sql_text := v_sql_text || sql_text(i);
end loop;
for i in 1.. ora_server_error_depth
loop
if i=1 then
insert into error_log(id,seq,tmstmp,username,errcode,msg,sql_text)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i), v_sql_text);
else
insert into error_log(id,seq,tmstmp,username,errcode,msg)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i) );
end if;
end loop;
commit;
end loop;
END;
/
select object_name,object_type,status from user_objects o where object_name='TRG_ERROR_LOGGING'
/
alter trigger trg_error_logging enable
/
As you can see it logs all errors into the table ERROR_LOG, but only if session parameter plsql_debug is set to true. Obviously, you can change it to own parameters or conditions.
So i have a stored procedure (that's been watered down below for demo purposes) that aren't passing any conditions and thus aren't inserting/passing any values into my table. I've tried converting the varchar/string that is being passed in by Java to a number but nothing is working. Below is my 'simplified code'
Create or Replace Procedure SAMPLE(rValue IN VARCHAR)
IS
v_Max value.value%type;
v_forecast value.value%type;
BEGIN
--
SELECT BUFFER_MAX_VALUE
INTO v_MAX
FROM look_up;
--
EXCEPTION
WHEN no_data_found
THEN SELECT 0
INTO v_forecast
FROM DUAL;
--
IF to_Number(rValue) < 0 OR to_Number(rValue) > v_MAX)
THEN
dbms_output.put_line('IF1 Works');
insert into value(value_id, value)
values(1, rValue);
ELSIF rValue is null OR to_Number(rValue) = 0
THEN
dbms_output.put_line('IF1A ONLY Works');
END IF;
ELSE
insert into value(value_id, value)
values(1, v_forecast);
dbms_output.put_line('IF1 ELSE ONLY Works');
END SAMPLE;
i tried passing the following in:
BEGIN
SAMPLE('-7');
END;
If the first SELECT BUFFER_MAX_VALUE returns anything, nothing else will be executed because you put absolutely everything into the EXCEPTION section. If you meant to handle that statement only, you should have enclosed it into its own BEGIN-END block, such as
create procedure ...
begin
-- its own begin starts now
begin
select buffer_max_value into v_max
from look_up;
exception
when no_data_found then
-- do something here
end;
-- its own end ends now
-- put the rest of your code here
end;
By the way, does LOOK_UP table contain no rows or only one row, always? Because, as SELECT you wrote contains no WHERE clause, it might raise TOO_MANY_ROWS (which you should also handle).
You declared rValue as VARCHAR2, and then apply TO_NUMBER to it. Why don't you declare it to be a NUMBER, instead? Because, nothing prevents you from passing, for example, 'XYZ' to the procedure, and then TO_NUMBER will miserably fail with the INVALID NUMBER error.
[EDIT: some more exception handling]
EXCEPTION section handles all exceptions that happen in that BEGIN-END block, no matter how many SELECT statements you have. Though, you won't know which one failed, unless you include a little bit of additional (simple) programming.
Note that this is just for showing what I meant; don't handle errors with DBMS_OUTPUT (as, most probably, nobody will see it), and rarely you'd want to handle errors with WHEN OTHERS.
create procedure ...
l_position number;
begin
l_position := 1;
select ... into ... from ...;
l_position := 2;
select ... into ...
exception
when others then
dbms_output.put_line('Error on position ' || l_position ||' '|| sqlerrm);
raise;
end;
As far as I can tell, you wanted the exception section to trap the situation where there is nothing in the lookup table. In that case, you set v_forecast and then continue. That means you need to put the select inside its own block.
I also avoiding multiple to_number calls by setting a constant.
I got rid of the unnecessary select from dual.
I also really really hope that you do not have a table named VALUE with a column named VALUE. Choose more meaningful names.
See how this works for you.
CREATE OR REPLACE PROCEDURE sample (rvalue IN VARCHAR2)
IS
c_rvalue CONSTANT NUMBER := rvalue;
v_max VALUE.VALUE%TYPE;
v_forecast VALUE.VALUE%TYPE;
BEGIN
BEGIN
SELECT buffer_max_value INTO v_max FROM look_up;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
v_forecast := 0;
END;
IF c_rvalue < 0 OR c_rvalue > v_max
THEN
DBMS_OUTPUT.put_line ('IF1 Works');
INSERT INTO VALUE (value_id, VALUE)
VALUES (1, rvalue);
ELSIF c_rvalue IS NULL OR c_rvalue = 0
THEN
DBMS_OUTPUT.put_line ('IF1A ONLY Works');
ELSE
INSERT INTO VALUE (value_id, VALUE)
VALUES (1, v_forecast);
DBMS_OUTPUT.put_line ('IF1 ELSE ONLY Works');
END IF;
END sample;
I have created a type to print experience of employees from employees table for 5 employees with one incorrect id. I have created an exception to skip it but I am not getting the result for it. What mistake have I done? Please refer my query and tell the error.
CREATE OR REPLACE TYPE emp_id_typ IS TABLE OF NUMBER;
/
CREATE OR REPLACE TYPE emp_exp_typ IS TABLE OF NUMBER;
/
CREATE OR REPLACE PROCEDURE exp_sp ( p_empid IN emp_id_typ
, p_exp OUT emp_exp_typ)
IS
v_exp NUMBER;
BEGIN
FOR i IN p_empid.FIRST..p_empid.COUNT
LOOP
p_exp.EXTEND;
SELECT ROUND(MONTHS_BETWEEN(SYSDATE, hire_date)/12) INTO v_exp FROM employees
WHERE employee_id = p_empid(i);
p_exp(p_exp.LAST) := v_exp;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
v_exp := NVL(v_exp, 'NULL');
END exp_sp;
/
DECLARE
v_empid emp_id_typ := emp_id_typ( );
v_exp emp_exp_typ := emp_exp_typ( );
BEGIN
v_empid := emp_id_typ(101, 102, 52, 100, 206);
FOR i IN v_empid.FIRST..v_empid.COUNT
LOOP
exp_sp(v_empid(i), v_exp(i));
dbms_output.put_line(v_empid(i), v_exp(i));
END LOOP;
END;
/
Thanks in advance...
You have several errors in both the procedure and the anonymous block. In the procedure:
You haven't initialised the OUT p_exp collection. As it's an OUT parameter you have to do it inside the procedure. (Alternatively make it IN OUT).
Your exception handler terminates the loop, so the v_exp you set in there is never used, and the p_exp collection is incomplete. (It's also bad practise to use when others.)
You can't assign a string like ``NULL'` to a number variable or collection. Either assign an a magic number that represents an error to the caller, or leave it null.
CREATE OR REPLACE PROCEDURE exp_sp ( p_empid IN emp_id_typ
, p_exp OUT emp_exp_typ)
IS
v_exp NUMBER;
BEGIN
-- have to initialise p_exp
p_exp := emp_exp_typ();
-- better to use either 1..count, or first..last; don't mix them
--FOR i IN p_empid.FIRST..p_empid.COUNT
FOR i IN p_empid.FIRST..p_empid.LAST
LOOP
p_exp.EXTEND;
-- you want the exception handler to deal with this row then continue,
-- so use a nested block
BEGIN
SELECT ROUND(MONTHS_BETWEEN(SYSDATE, hire_date)/12) INTO v_exp FROM employees
WHERE employee_id = p_empid(i);
EXCEPTION
WHEN OTHERS THEN
-- you can't assign a string to a number
--v_exp := NVL(v_exp, 'NULL');
-- just an example as not clear what you need
v_exp := -1;
END; -- nested block with exception handler
p_exp(p_exp.LAST) := v_exp;
END LOOP;
END exp_sp;
/
And in your anonymous block:
The initialisation of v_exp is redundant as the procedure argument is OUT. Not an error but pointless.
You are calling the procedure inside the loop, passing a single element; it should be called once outside the loop and passed the whole collection(s).
Your call to dbms_output.put_line is wrong; that only takes a single argument, so you have to build a string up e.g. with concatenation.
DECLARE
v_empid emp_id_typ := emp_id_typ( );
-- assignment is redundant
-- v_exp emp_exp_typ := emp_exp_typ( );
v_exp emp_exp_typ;
BEGIN
v_empid := emp_id_typ(101, 102, 52, 100, 206);
-- moved this call up, without indexing
exp_sp(v_empid, v_exp);
-- better to use either 1..count, or first..last; don't mix them
-- FOR i IN v_empid.FIRST..v_empid.COUNT
FOR i IN v_empid.FIRST..v_empid.LAST
LOOP
--exp_sp(v_empid(i), v_exp(i));
-- fixed call to dbms_output
dbms_output.put_line(v_empid(i) ||': '|| v_exp(i));
END LOOP;
END;
/
which gets:
101: 28
102: 25
52: -1
100: 30
206: 24
PL/SQL procedure successfully completed.
I need not to print the third variable as it is not in the table.
You can test the v_exp value for the magic number (or null, which is safer, but the procedure needs to be modified slightly for that), and only print the results which you need:
...
FOR i IN v_empid.FIRST..v_empid.LAST
LOOP
IF v_exp(i) != -1 THEN
dbms_output.put_line(v_empid(i) ||': '|| v_exp(i));
END IF;
END LOOP;
...
which gets:
101: 28
102: 25
100: 30
206: 24
PL/SQL procedure successfully completed.
Wrote the following PLSQL script to produce a report and am getting the error messages
Error at line 5
ORA-06550: line 61, column 18:
PLS-00103: Encountered the symbol "1" when expecting one of the following:
(
I've been through the code many times and I cannot find the error. Any help would be greatly appreciated.
I'm currently working in Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
SET serveroutput ON size 1000000;
DECLARE
TYPE TITLE_RECORD_TYPE IS RECORD
(id number(19),
gaid varchar2(20),
artist_legal_name varchar2(510),
artist_display_title varchar2(510),
display_title varchar2(510),
category varchar2(255),
type varchar2(255),
sub_type varchar2(255));
TITLE_RECORD TITLE_RECORD_TYPE;
v_title varchar2(510);
v_artist varchar2(510);
v_total_rows_error number(20) := 0;
v_row_count number(10) := 0;
v_error_desc varchar2(200) := NULL;
v_error_code number(19);
CURSOR ARTIST_TITLE_CURSOR is
select track_artist,track_title
from asset_artist_title;
CURSOR QUERY_CURSOR is
select distinct g1.gaid,g2.legal_name,g1.artist_display_title,
g1.display_title,g1.category,g1.type,g1.sub_type
from gcdm_app_rpt.rpt_asset g1,
gcdm_app_rpt.rpt_artist g2
where g1.artist_id = g2.id
and g1.is_deleted <> 'Y'
and g1.is_core = 'Y'
and g2.is_core = 'Y'
and g1.title like v_title||'%'
and g1.artist_display_title like v_artist||'%';
BEGIN
OPEN ARTIST_TITLE_CURSOR;
LOOP
FETCH ARTIST_TITLE_CURSOR into v_artist,v_title;
EXIT WHEN ARTIST_TITLE_CURSOR%NOTFOUND or ARTIST_TITLE_CURSOR%NOTFOUND IS NULL;
SELECT count(*)
INTO v_row_count
FROM gcdm_app_rpt.rpt_asset g1,
gcdm_app_rpt.rpt_artist g2
WHERE g1.artist_id = g2.id
AND g1.is_core = 'Y'
AND g1.is_deleted <> 'Y'
AND g2.is_core = 'Y'
AND g1.title like v_title||'%'
AND g1.artist_display_title like v_artist||'%';
IF v_row_count < 1 THEN
v_error_desc := 'Matching Asset record for '||v_artist||' - '||v_title||' not found';
DBMS_OUTPUT.PUT_LINE('Error: '||v_error_desc||'.');
v_row_count := 0;
v_total_rows_error := v_total_rows_error + 1;
ELSE
OPEN QUERY_CURSOR
FOR i in 1..ARTIST_TITLE_CURSOR
LOOP
FETCH QUERY_CURSOR into TITLE_RECORD;
EXIT WHEN QUERY_CURSOR%NOTFOUND or QUERY_CURSOR%NOTFOUND IS NULL;
DBMS_OUTPUT.PUT_LINE(title_record.id,title_record.gaid,title_record.artist_legal_name,title_record.artist_display_name,
title_record.display_title,title_record.category,title_record.type,title_record.sub_type);
END LOOP;
CLOSE QUERY_CURSOR;
v_row_count := 0;
END IF;
END LOOP;
CLOSE ARTIST_TITLE_CURSOR;
DBMS_OUTPUT.PUT_LINE(chr(0));
IF v_total_rows_error > 0 THEN
DBMS_OUTPUT.PUT_LINE('Total Rows in error: '||v_total_rows_error);
END IF;
DBMS_OUTPUT.PUT_LINE(CHR(0));
EXCEPTION
WHEN OTHERS THEN
v_error_desc := SQLERRM;
v_error_code := SQLCODE;
DBMS_OUTPUT.PUT_LINE('Error: '||v_error_desc||' - '||v_error_code);
END;
It's line 67 in what you've posted, not 61, but still; this line is not right:
FOR i in 1..ARTIST_TITLE_CURSOR
You're trying to loop over a range of numbers - perhaps you wanted the number of records returned by the cursor, which you can't get - but your end 'number' is a cursor, so not legal in that context.
But it seems to be completely out of place anyway as you're looping over the QUERY_CURSOR records, so I wouldn't think the ARTIST_TITLE_CURSOR is relevant at this point. And you aren't attempting to use i. It looks like you can just remove that line.
More importantly, the previous line is missing a semi-colon:
OPEN QUERY_CURSOR;
Because it doesn't have one it's seeing the FOR and expecting a cursor query.
Following up on comments about why you have that FOR 1..v_row_count, it's still a bit redundant. You're limiting the number of fetches you do to match the count you got previously, from essentially the same query as you have in the cursor, which means you don't quite ever hit the EXIT WHEN QUERYCURSOR%NOTFOUND condition - that would come from the v_row_count+1 loop iteration. Normally you wouldn't know how many rows you expect to see before you loop over a cursor.
You don't really need to know here. The count query is repetitive - you're querying the same data you then have to hit again for the cursor, and you have to maintain the query logic in two places. It would be simpler to forget the count step, and instead keep a counter as you loop over the cursor; then handle the zero-rows condition after the loop. For example:
DECLARE
...
BEGIN
OPEN ARTIST_TITLE_CURSOR;
LOOP
FETCH ARTIST_TITLE_CURSOR into v_artist,v_title;
EXIT WHEN ARTIST_TITLE_CURSOR%NOTFOUND;
-- initialise counter for each ARTIST_TITLE
v_row_count := 0;
OPEN QUERY_CURSOR;
LOOP
FETCH QUERY_CURSOR into TITLE_RECORD;
EXIT WHEN QUERY_CURSOR%NOTFOUND;
-- increment 'found' counter
v_row_count := v_row_count + 1;
DBMS_OUTPUT.PUT_LINE(title_record.id
||','|| title_record.gaid
||','|| title_record.artist_legal_name
||','|| title_record
||','|| artist_display_name
||','|| title_record.display_title
||','|| title_record.category
||','|| title_record.type
||','|| title_record.sub_type);
END LOOP;
CLOSE QUERY_CURSOR;
-- now check if we found anything in the QUERY_CURSOR loop
IF v_row_count < 1 THEN
v_error_desc := 'Matching Asset record for '||v_artist||' - '||v_title||' not found';
DBMS_OUTPUT.PUT_LINE('Error: Matching Asset record for '
|| v_artist || ' - ' || v_title || ' not found.');
v_total_rows_error := v_total_rows_error + 1;
END IF;
END LOOP;
CLOSE ARTIST_TITLE_CURSOR;
--DBMS_OUTPUT.PUT_LINE(chr(0));
-- presumably this was meant to put out a blank line; use this instead
DBMS_OUTPUT.NEW_LINE;
IF v_total_rows_error > 0 THEN
DBMS_OUTPUT.PUT_LINE('Total Rows in error: '||v_total_rows_error);
END IF;
--DBMS_OUTPUT.PUT_LINE(CHR(0));
DBMS_OUTPUT.NEW_LINE;
END;
I've also taken out the exception handler because it isn't really adding anything; you'd see the code and message without it, even if you didn't have server output on; and catching WHEN OTHERS is a bad habit to get into.
You also don't need to declare your record type. You could use an implicit cursor anyway and avoid the type and variable completely, but even with the cursor definition you have, you could put this afterwards instead:
TITLE_RECORD QUERY_CURSOR%ROWTYPE;
There are various ways to open and loop over cursors, and you're using one of the more explicit ones - which isn't a bad thing for learning about them, but be aware of the options too.
Here is my problem. I am looping through some values and some of those values raise an exception. I want to log those values, but the program flow should not break. I mean, if I encounter such a value, I will simply log the error and skip to the next value.
here is the simplified version :
drop table test;
--destination Table
create table test
(
id varchar2(2)
);
-- Error log table
create table test_log
(
id varchar2(10)
);
DECLARE
l_num NUMBER;
BEGIN
FOR c IN 90..102
LOOP
INSERT INTO test
VALUES (c);
l_num:=c;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(l_num);
INSERT INTO test_log
VALUES (l_num);
COMMIT;
--raise;
END;
/
My problem is, when it's encountering an error, it simply jumps to the exception section and not looping through the later values in the loop.
You can catch the exception in an inner block:
DECLARE
l_num NUMBER;
BEGIN
FOR c IN 90..102
LOOP
l_num:=c;
BEGIN -- inner block
INSERT INTO test
VALUES (c);
EXCEPTION -- in inner block
WHEN OTHERS THEN
dbms_output.put_line(l_num);
INSERT INTO test_log
VALUES (l_num);
END; -- inner block
END LOOP;
END;
/
The loop won't be interrupted if an exception occurs; only that single insert is affected. Note that you don't really want to commit inside the exception handler as that will commit all the successful inserts so far, not just the error. (If you wanted to log the errors but later roll back you could use an autonomous procedure to do the logging, but that's a separate discussion). And you don't want to re-raise the exception as that would still break the loop and the whole outer anonymous block.
Catching 'others' is generally not a good idea; if you have known errors you could encounter - like a bad data or number format - it's better to catch those explicitly. If you have a wider problem like not being able to extend a data file then the insert inside the exception handler would presumably fail anyway though.
You don't really need l_num any more as c is still in-scope for the inner exception handler, so you could simplify slightly to:
BEGIN
FOR c IN 90..102
LOOP
BEGIN -- inner block
INSERT INTO test
VALUES (c);
EXCEPTION -- in inner block
WHEN OTHERS THEN
dbms_output.put_line(c);
INSERT INTO test_log
VALUES (c);
END; -- inner block
END LOOP;
END;
/
You can also use FORALL.
That answer to your request with SAVE EXCEPTION statement because the FORALL loop to continue even if some DML operations fail. And it will be more efficient than a simple LOOP.
Ex :
DECLARE
CURSOR LCUR$VAL IS
SELECT ID
FROM test1;
TYPE LT$TAB IS TABLE OF TEST%ROWTYPE;
LA$TAB_TEST LT$TAB;
dml_errors EXCEPTION;
PRAGMA EXCEPTION_INIT(dml_errors, -24381);
LC$ERRORS NUMBER(11);
LC$ERRNO NUMBER(11);
LC$MSG VARCHAR2(4000 CHAR);
LC$IDX NUMBER(11);
BEGIN
OPEN LCUR$VAL;
LOOP
FETCH LCUR$VAL BULK COLLECT INTO LA$TAB_TEST LIMIT 1000;
BEGIN
FORALL x IN 1 .. LA$TAB_TEST.COUNT SAVE EXCEPTIONS
INSERT INTO test VALUES LA$TAB_TEST(x);
EXIT WHEN LCUR$VAL%NOTFOUND;
EXCEPTION
WHEN DML_ERRORS THEN
LC$ERRORS := sql%BULK_EXCEPTIONS.COUNT;
FOR idx IN 1 .. LC$ERRORS
LOOP
LC$ERRNO := sql%BULK_EXCEPTIONS (idx).ERROR_CODE;
LC$MSG := sqlerrm(-LC$ERRNO);
LC$IDX := sql%BULK_EXCEPTIONS(idx).error_index;
-- here you can log in table : test_log...
END LOOP;
END;
END LOOP;
CLOSE LCUR$VAL;
END;
Hoping that it can help.