Duplicate error message when error handling - oracle

I have this procedure:
CREATE OR REPLACE PROCEDURE procA (paramA VARCHAR2, paramB VARCHAR2, output_value OUT VARCHAR2)
IS
BEGIN
IF paramA IS NOT NULL THEN
BEGIN --1
SELECT columA
INTO output_value
FROM tableA
WHERE columnB = paramA;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- 2
SELECT columB
INTO output_value
FROM tableB
WHERE columnC = paramB;
END;
ELSE
SELECT columB
INTO output_value
FROM tableB
WHERE columnC = paramB;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR (-20008, 'Error: ' || SQLCODE || ' ' || SUBSTR (SQLERRM, 1, 200));
END procA;
I run this call:
DECLARE
output VARCHAR2 (10);
BEGIN
procA ('valueA', 'valueB', output);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM); --3
END;
With these parameters the first SELECT statement gets a NO_DATA_FOUND (1). And handled with the that EXCEPTION. Then, I run the second SELECT statement (2) and I get again, a NO_DATA_FOUND exception.
Why do I get a duplicate NO_DATA_FOUND message in (3), like this:
ORA-20008: Error: 100 ORA-01403: no data found
ORA-01403: no data found
The first exception is handled, I expected that the last error handling will return just one message.

You aren't seeing a single error being duplicated; you're seeing two errors, which happen to be the same.
If you add a format_error_stack call to your final exception hander you can see that both of the thrown exceptions are in that stack:
...
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK);
RAISE_APPLICATION_ERROR (-20008, 'Error: ' || SQLCODE || ' ' || SUBSTR (SQLERRM, 1, 200));
END procA;
/
The call from your anonymous block then shows:
ORA-01403: no data found
ORA-06512: at "<schema>.PROCA", line 13
ORA-01403: no data found
ORA-06512: at "<schema>.PROCA", line 6
ORA-20008: Error: 100 ORA-01403: no data found
ORA-01403: no data found
db<>fiddle
It might be a bit clearer if you vary the errors, e.g.:
declare
l_dummy varchar2(1);
begin
begin
select 'x' into l_dummy from all_users;
exception when too_many_rows then
select 'x' into l_dummy from all_users where 1=0;
end;
exception when others then
dbms_output.put_line(dbms_utility.format_error_stack);
dbms_output.put_line('caught <' || sqlerrm || '>');
end;
/
which produces:
ORA-01403: no data found
ORA-06512: at line 7
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 5
caught <ORA-01403: no data found
ORA-01422: exact fetch returns more than requested number of rows>
db<>fiddle
The SQLERRM call combines both error messages, though the documentation doesn't seem to say that will happen. Oracle support note 2438311.1 seems to suggest it shouldn't, but it does all of the db<>fiddle versions and in 12.2.0.1 and 19.11 at least.
But, what if I just want the last one?
If you're on Oracle 12c or later, you can use UTL_CALL_STACK.ERROR_MSG(1) to get the first error from the stack, instead of using SQLERRM.
...
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR (-20008,
'Error: ' || SQLCODE || ' ' || SUBSTR (UTL_CALL_STACK.ERROR_MSG(1), 1, 200));
END procA;
/
which will now just show:
ORA-20008: Error: 100 no data found
db<>fiddle (and another one for my earlier example.)
You can read more about that function in the documentation.

There is a difference between handling and catching an exception.
You're using RAISE_APPLICATION_ERROR, which throws the exception to the caller although you've caught it once and since you catch it two times - you have two errors.

Related

migrate raiserror from SYBASE ASE to ORACLE

I am migrating stored procedures to pl/sql blocks, and I have little knowledge in error handling in oracle and nothing in sybase can you help me.
example: sql SYBASE
DELETE table_1
WHERE N=0
SELECT #myrowcount = ##rowcount, #myerror = ##error, #mystat = ##sqlstatus
if (#myerror <> 0)
begin
raiserror 40900 "Error: When Generating Exception List #table_1 (error= %1!)", #mystat
select #cod_err= 1
return #cod_err
end
Edit: sql oracle i dont know if this is right
begin
DELETE table_1
WHERE N=0
EXCEPTION WHEN OTHERS THEN
SWV_error := SQLCODE;
end;
v_mi_error := SWV_error;
if v_mi_error != 0 then
RAISE_APPLICATION_ERROR(-40900,'Error: When Generating Exception List table_1');
return;
end if;
Which error do you expect for delete? It'll either delete some rows, or won't. If table (or column) doesn't exist, code wouldn't even compile so you wouldn't reach runtime error.
Anyway: in Oracle, it looks like this:
begin
delete table_1 where n = 0;
exception
when others then
raise_application_error(-20000, 'Error: ' || sqlerrm);
end;
/
others is exception which handles various things; you don't really care which error it is, as could be any error. Oracle reserved error codes from -20000 to -20999 for us, developers so you have to pick one of these (which means that -40900 won't work).
sqlerrm is error message (its description). If you want, you can get its code via sqlcode.
Example which shows how it actually works (not with delete, though) is query that fetches employee name for non-existent employee number. That raises predefined no_data_found error (whose code is -01403) so you could handle it, directly, or - as my previous example shows - use others.
SQL> declare
2 l_name varchar2(10);
3 begin
4 select ename
5 into l_name
6 from emp
7 where empno = -1;
8 exception
9 when others then
10 raise_application_error(-20000, 'Error: ' || sqlcode ||': '|| sqlerrm);
11 end;
12 /
declare
*
ERROR at line 1:
ORA-20000: Error: 100: ORA-01403: no data found
ORA-06512: at line 10
SQL>

Oracle uses exception handler from first block in later blocks

I'm running into a behavior where I'm trying to use case-specific exception handlers for several Oracle PL/SQL blocks in a Flyway script and Oracle, apparently contradicting its documented scoping for exception handlers, sends all exceptions to the exception handler for the first block. For example, in this code:
begin
begin
execute immediate '
create table "test" (
"id" number not null,
"name" varchar2(100) not null,
constraint "test_pk" primary key ("id")
)
';
exception
when others then
if sqlcode != -955 then raise; end if;
end;
begin
execute immediate 'fail to create index "test_name_idx" on "test" ("name")';
exception
when others then
if sqlcode != -6512 then raise; end if;
end;
end;
the ORA-06512 exception is not caught, and the exception raised is tagged as from line 13.
Wrapping the blocks in more blocks doesn't help.
What is going on here? How do I stop this from happening?
This seems to be a bug, which has (so far) been reproduced in 11.2.0.4, 12.1.0.2 and 12.2.0.1. It doesn't seem to require DDL, or any real action in the first sub-block (though just doing null; as a placeholder doesn't trigger it, possibly because the compiler removes it), but it does seem to need the if inside both exception handlers:
begin
begin
dbms_output.put_line('Dummy message');
exception
when others then
dbms_output.put_line('In first exception handler');
if 1=1 then
raise;
end if;
end;
begin
execute immediate 'invalid';
exception
when others then
dbms_output.put_line('In second exception handler');
if 1=1 then
raise;
end if;
end;
end;
/
Dummy message
In second exception handler
ORA-00900: invalid SQL statement
ORA-06512: at line 8
ORA-06512: at line 13
As with your example the exception is thrown by line 13 so should be reported as (re-)raised at line 18; but it's instead it's reported as raised from line 8, which doesn't make sense. (The at line 13 message is only shown in 12.2; in 11.2 and 12.1 it only reports the first ORA-06512, which is rather more confusing. At least in 12 2 you have some clue where the problem really is.)
From the debugs you can see it doesn't actually use the first exception handler, and it does go into the second one. It 'only' seems to be reporting against the wrong line number, rather than executing the wrong code.
It appears that doing real work inside the if, immediately before the raise somehow fixes things - in either exception handling section; this adds a message in the first, which can't be reached:
begin
begin
dbms_output.put_line('Dummy message');
exception
when others then
dbms_output.put_line('In first exception handler');
if 1=1 then
dbms_output.put_line('This avoids the bug somehow');
raise;
end if;
end;
begin
execute immediate 'invalid';
exception
when others then
dbms_output.put_line('In second exception handler');
if 1=1 then
raise;
end if;
end;
end;
/
Dummy message
In second exception handler
ORA-00900: invalid SQL statement
ORA-06512: at line 19
ORA-06512: at line 14
and this in the second:
begin
begin
dbms_output.put_line('Dummy message');
exception
when others then
dbms_output.put_line('In first exception handler');
if 1=1 then
raise;
end if;
end;
begin
execute immediate 'invalid';
exception
when others then
dbms_output.put_line('In second exception handler');
if 1=1 then
dbms_output.put_line('This avoids the bug somehow');
raise;
end if;
end;
end;
/
Dummy message
In second exception handler
ORA-00900: invalid SQL statement
ORA-06512: at line 19
ORA-06512: at line 13
In both cases the reported line number is now correct. Somehow.
It doesn't have to be a dbms_output call, anything seems to work, such as a dummy procedure call or query, even an extra sub-block (e.g. begin execute immediate 'select * from dual'; end;, even though the query isn't executed because there's no into...). Again just using null; doesn't work though.
This is a bit ugly but gives you a way to stop it from happening at least, sort of.
It's clearly weird and unexpected and inconsistent behaviour, and has been around for a while, so it should probably be raised as a service request through My Oracle Support. I can't see any existing reports but I didn't look very hard so there might be one lurking somewhere.

How to get the stacktrace for the original exception in oracle PL/SQL from a RAISED exception?

I'm having an issue where the original stack trace gets lost when I catch an exception, then raise it.
Exception gets thrown in proc_a
Catch the exception.
Perform a rollback.
RAISE the exception.
Catch the exception (parent block)
Print Stack Trace: SUBSTR(SQLERRM || chr(10) || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(), 1, 3999)
Example:
DECLARE
BEGIN
DECLARE
lv_val VARCHAR2(1);
BEGIN
SELECT dummy INTO lv_val -- Line# 6 (desired)
FROM dual
WHERE dummy = 'FFF';
EXCEPTION
WHEN OTHERS THEN
--DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM || chr(10) || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(), 1, 3999));
RAISE; -- Line# 12 (actual)
END;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM || chr(10) || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(), 1, 3999));
END;
/
Desired Result:
The line number for the original exception (step 1).
ORA-01403: no data found
ORA-06512: at line 6
or
ORA-01403: no data found
ORA-06512: at line 12
Caused By:
ORA-01403: no data found
ORA-06512: at line 6
Actual Result:
The line number for the RAISE (Step 4).
ORA-01403: no data found
ORA-06512: at line 12
Additional attempts that failed:
SQLERRM || chr(10) || DBMS_UTILITY.FORMAT_ERROR_STACK()
ORA-01403: no data found
ORA-01403: no data found
SQLERRM || chr(10) || DBMS_UTILITY.FORMAT_CALL_STACK()
ORA-01403: no data found
----- PL/SQL Call Stack -----
object line object
handle number name
0xee1cbd68 18 anonymous block
In your inner exception handler, instead of using the RAISE procdure, use the RAISE_APPLICATION_ERROR procedure passing it the results of the dbms_utility.format_error_backtrace function to get the original line number:
BEGIN
DECLARE
lv_val VARCHAR2(1);
BEGIN
SELECT dummy INTO lv_val -- Line# 6 (desired)
FROM dual
WHERE dummy = 'FFF';
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, dbms_utility.format_error_backtrace,true);
END;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM || chr(10) || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(), 1, 3999));
END;
/
Without the outer exception handler you'll get the following error report:
Error report -
ORA-20001: ORA-06512: at line 5
ORA-06512: at line 10
ORA-01403: no data found
With outer exception handler you'll get the following:
ORA-20001: ORA-06512: at line 5
ORA-01403: no data found
ORA-06512: at line 10
The message ordering is slightly different, but the info is still all there.

How to pass an error details in PL/SQL exception propagation without a package state?

Currently I'm using the following pattern to record error details in PL/SQL exception propagation. Please see the commented code below for the details. I'm happy with it as the error handling code doesn't clutter the whole code base and all the details why an error was triggered can be recorded.
Unfortunately there is an unwanted side-effect of package state introduced by v_error variable.
How I can pass an error detail information in PL/SQL exception propagation without introducing a package state ? (I want to eliminate package state to make deployment easier.)
Using different exceptions like rule_2_failure_ex and rule_3_failure_ex is not the solution I'm looking for as
there is no need to handle the error conditions differently
for troubleshooting it is very important to be able to record arbitrary information
(I'm already using a logging so the error condition information is available, but it's not in "the right place".)
I'm looking for Oracle 11g solution but 12c solution (if different than in 11g) is also welcome as one day I might end working with 12c too (personally I don't care about 10g).
-- this is a simplied example to address to question only
create or replace package so50 is
procedure run(p_num in number);
end;
/
show errors
create or replace package body so50 is
processing_failure_ex exception;
-- package state that I'd like to eliminate
v_error varchar2(32767);
-- in reality the processing and details are more complex
procedure p3(p_num in number) is
begin
if p_num = 3
then
-- it's important to be able to record arbitrary information at this point
v_error := 'Failed to process rule 3: (p_num = ' || p_num || ')';
raise processing_failure_ex;
end if;
end;
-- the comments on p3 apply
procedure p2(p_num in number) is
begin
if p_num = 2
then
v_error := 'Failed to process rule 2: (p_num = ' || p_num || ')';
raise processing_failure_ex;
end if;
end;
procedure p1(p_num in number) is
begin
p2(p_num);
p3(p_num);
exception
when others then
v_error := v_error
|| ' Additional details of failure.';
raise;
end;
procedure run(p_num in number) is
begin
v_error := null;
begin
p1(p_num);
exception
when processing_failure_ex then
-- in reality an error recovery will be tried first and only then
-- the error will be forwarded to a monitoring framework that will
-- raise an alert for human action
dbms_output.put_line('Error details: ' || v_error);
raise;
end;
exception
when others then
-- out of the scope of the question
raise;
end;
end;
/
show errors
You could use raise_application_error with an error code tied to your exception:
create or replace package body so50 is
processing_failure_ex exception;
pragma exception_init(processing_failure_ex, -20999);
And raise with the message you want:
raise_application_error(-20999,
'Failed to process rule 3: (p_num = ' || p_num || ')', true);
When you want to store the whole stack you can use dbms_utility.format_error_stack:
dbms_output.put_line('Error details:');
dbms_output.put_line(dbms_utility.format_error_stack);
So removing v_error altogether:
create or replace package so50 is
procedure run(p_num in number);
end;
/
create or replace package body so50 is
processing_failure_ex exception;
pragma exception_init(processing_failure_ex, -20999);
-- in reality the processing and details are more complex
procedure p3(p_num in number) is
begin
if p_num = 3
then
-- it's important to be able to record arbitrary information at this point
raise_application_error(-20999,
'Failed to process rule 3: (p_num = ' || p_num || ')', true);
end if;
end;
-- the comments on p3 apply
procedure p2(p_num in number) is
begin
if p_num = 2
then
raise_application_error(-20999,
'Failed to process rule 2: (p_num = ' || p_num || ')', true);
end if;
end;
procedure p1(p_num in number) is
begin
p2(p_num);
p3(p_num);
exception
when others then
raise_application_error(-20999,
'Additional details of failure', true);
end;
procedure run(p_num in number) is
begin
begin
p1(p_num);
exception
when processing_failure_ex then
-- in reality an error recovery will be tried first and only then
-- the error will be forwarded to a monitoring framework that will
-- raise an alert for human action
dbms_output.put_line('Error details:');
dbms_output.put_line(dbms_utility.format_error_stack);
raise;
end;
exception
when others then
-- out of the scope of the question
raise;
end;
end;
/
Calling that gets:
SQL> set serveroutput on
SQL> exec so50.run(1);
PL/SQL procedure successfully completed.
SQL> exec so50.run(2);
ORA-20999: Additional details of failure
ORA-06512: at "STACKOVERFLOW.SO50", line 42
ORA-20999: Failed to process rule 2: (p_num = 2)
ORA-06512: at "STACKOVERFLOW.SO50", line 64
ORA-06512: at line 1
Error details:
ORA-20999: Additional details of failure
ORA-06512: at "STACKOVERFLOW.SO50", line 42
ORA-20999: Failed to process rule 2: (p_num = 2)
SQL> exec so50.run(3);
ORA-20999: Additional details of failure
ORA-06512: at "STACKOVERFLOW.SO50", line 42
ORA-20999: Failed to process rule 3: (p_num = 3)
ORA-06512: at "STACKOVERFLOW.SO50", line 64
ORA-06512: at line 1
Error details:
ORA-20999: Additional details of failure
ORA-06512: at "STACKOVERFLOW.SO50", line 42
ORA-20999: Failed to process rule 3: (p_num = 3)
In both cases the stack trace before the 'Error details:' is coming from the final out-of-scope raise; if that was temporarily squashed (just for a demo, not suggesting you really squash it!) you'd just see:
SQL> exec so50.run(3);
PL/SQL procedure successfully completed.
Error details:
ORA-20999: Additional details of failure
ORA-06512: at "STACKOVERFLOW.SO50", line 42
ORA-20999: Failed to process rule 3: (p_num = 3)
You can use different exception numbers for the various procedures and scenarios of course, I've just used a common one to simplify things for now. They only need to be named (tied with a name via a pragma) if you want to catch them by name. And if you do you could have all the exceptions defined in one place.
My current solution based on #AlexPoole answer:
Exception Package
-- encapsulates the uglyness to keep calling code clean
create or replace package so50_ex is
-- exception type and error code reserved for this purpose only
general_ex exception;
general_ex_code constant number := -20999;
pragma exception_init(general_ex, -20999);
procedure raise(p_msg in varchar2, p_ex_code in number default -20999);
function full_error_stack return varchar2;
end;
/
show errors
create or replace package body so50_ex is
procedure raise(p_msg in varchar2, p_ex_code in number default -20999) is
begin
raise_application_error(p_ex_code,
substrb(p_msg, 1, 2048),
true);
end;
function full_error_stack return varchar2 as
-- will always fit to buffer as format_error_stack returns 2000 bytes at
-- maximum
v_stack varchar2(32767) := dbms_utility.format_error_stack;
begin
-- might not fit to buffer as format_error_backtrace return length is not
-- limited
v_stack := v_stack ||
substrb(dbms_utility.format_error_backtrace, 1, 30767);
return v_stack;
end;
end;
/
show errors
Usage Example
create or replace package so50 is
-- a user can always have his own exceptions
processing_failure_ex exception;
processing_failure_ex_code constant number := -20100;
pragma exception_init(processing_failure_ex, -20100);
procedure run(p_num in number);
end;
/
show errors
create or replace package body so50 is
procedure p3(p_num in number) is
begin
if p_num = 3
then
-- use specific exception
so50_ex.raise('Failed to process rule 3: (p_num = ' || p_num || ')',
processing_failure_ex_code);
end if;
end;
procedure p2(p_num in number) is
begin
if p_num = 2
then
-- use default exception
so50_ex.raise('Failed to process rule 2: (p_num = ' || p_num || ')');
end if;
end;
procedure p1(p_num in number) is
begin
p2(p_num);
p3(p_num);
exception
when processing_failure_ex then
dbms_output.put_line('ERROR RECOVERED SUCCESFULLY.');
dbms_output.put_line('DETAILS:');
dbms_output.put_line(so50_ex.full_error_stack);
when others then
so50_ex.raise('Additional details of failure.');
end;
procedure run(p_num in number) is
begin
p1(p_num);
exception
when others then
dbms_output.put_line('EXCEPTION: ' || so50_ex.full_error_stack);
raise;
end;
end;
/
show errors

The use of sql%notfound on implicit cursor

I'm completely new to oracle PLSQL. Any help is appreicated.
I could not find similiar question on SO (Maybe it's too basic?)
I'm running the code from TOAD, Oracle 11G
SET SERVEROUTPUT ON
DECLARE
var titres%ROWTYPE;
BEGIN
select reference, sicovam into
var.reference, var.sicovam
from titres
where reference = '1234';
if sql%notfound then
dbms_output.put_line('NOT FOUND');
else
dbms_output.put_line(var.reference || ' ' || var.sicovam);
end if;
END;
If the Where Clause can extract one row of data, then it will run the else part
If the Where Clause cannot extract any row, then it will display the error:
ORA-01403: no data found
ORA-06512: at line 4
Can anyone point me in the right direction? Thanks
I have tried using the basic exception handling code
When others then
null;
end;
Then I am getting another strange result:
If the Where Clause can extract one row of data, then it will NOT run the else part or the if part.
When a query inside a pl/sql block returns no rows the NO_DATA_FOUND exception is raised immediately and execution of the block will halt. So the if sql%notfound then condition will never be evaluated. To catch that exception and respond accordingly, you need EXCEPTION section.
SET SERVEROUTPUT ON
DECLARE
var titres%ROWTYPE;
BEGIN
-- In this case you have to be sure that the query returns only one row
-- otherwise the exception ORA-01422 will be raised
select reference, sicovam into
var.reference, var.sicovam
from titres
where reference = '1234';
dbms_output.put_line(var.reference || ' ' || var.sicovam);
EXCEPTION
WHEN NO_DATA_FOUND
THEN dbms_output.put_line('NOT FOUND');
END;
with select into you need to use exceptions NO_DATA_FOUND and TOO_MANY_ROWS
SET SERVEROUTPUT ON
DECLARE
var titres%ROWTYPE;
BEGIN
select reference, sicovam into
var.reference, var.sicovam
from titres
where reference = '1234';
dbms_output.put_line(var.reference || ' ' || var.sicovam);
exception
when no_data_found
then
dbms_output.put_line('NOT FOUND');
when too_many_rows
then
dbms_output.put_line('2+ ROWS found');
END;

Resources