I am sure my question has a simple theoretical answer but I cannot find it.
I have a procedure which accepts as parameter a NUMBER. It also have the VALUE_ERROR and OTHERS exceptions:
create or replace procedure test( p1 number) is
begin
null;
exception
when VALUE_ERROR then
RAISE_APPLICATION_ERROR(-20001, 'value_error');
when others then
RAISE_APPLICATION_ERROR(-20000, 'others');
end;
I am executing the procedure with a VARCHAR2 paramter:
execute test('a');
... an I expect that the error message displayed to be
ORA-20001 value_error
but, unfortunately, I got:
Error report - ORA-06502: PL/SQL: numeric or value error: character to
number conversion error ORA-06512: at line 1
06502. 00000 - "PL/SQL: numeric or value error%s"
*Cause: An arithmetic, numeric, string, conversion, or constraint error
occurred. For example, this error occurs if an attempt is made to
assign the value NULL to a variable declared NOT NULL, or if an
attempt is made to assign an integer larger than 99 to a variable
declared NUMBER(2).
*Action: Change the data, how it is manipulated, or how it is declared so
that values do not violate constraints.
Can anyone explain this, or share a link where it is explained why I do not receive expected error message?
Thank you very much,
As Nicholas mentioned you don't get your message because the exception is thrown not inside the procedure but before executing it.
Let's look at an example:
create or replace procedure test( p1 number) is
begin
null;
exception
when VALUE_ERROR then
RAISE_APPLICATION_ERROR(-20001, 'PROC value_error');
when others then
RAISE_APPLICATION_ERROR(-20000, 'PROC others');
end;
/
begin
test('a');
exception
when VALUE_ERROR then
RAISE_APPLICATION_ERROR(-20001, 'OUT value_error');
when others then
RAISE_APPLICATION_ERROR(-20000, 'OUT others');
end;
What happens here is that you're calling a procedure that requires number as parameter so Oracle tries conversion during execution of anonymous block. You can't see the message from the procedure because before entering the procedure the conversion exception is thrown.
Now let's see what happens if we change the procedure:
create or replace procedure test( p1 varchar2) is
param number;
begin
param := p1;
exception
when VALUE_ERROR then
RAISE_APPLICATION_ERROR(-20001, 'PROC value_error');
when others then
RAISE_APPLICATION_ERROR(-20000, 'PROC others');
end;
/
begin
test('a');
exception
when VALUE_ERROR then
RAISE_APPLICATION_ERROR(-20001, 'OUT value_error');
when others then
RAISE_APPLICATION_ERROR(-20000, 'OUT others');
end;
Or just:
begin
test('a');
end;
to see the error thrown in the procedure.
Now the procedure requires number within its body; when execution reaches that point it throws the conversion error, from within the procedure itself.
Related
We can pass error message, error code to front end using USER_DEFINED EXCEPTION or PREDEFINED EXCEPTION (others, SQLERRM, SQLCODE) using OUT PARAMETER MODE, then why we use RAISE_APPLICATION_ERROR procedure?
What is the difference between RAISE_APPLICATION_ERROR and pragma EXCEPTION_INIT?
I have Googled these questions, but can't get a clear answer - that is why posted here.
See this similar question. The answer to both of those is that RAISE_APPLICATION_ERROR is the canonical way to define custom error messages to be displayed in the client application. Yes, you could also pass custom messages back using an OUT parameter, or DBMS_OUTPUT, or something similar, but those are not standardized - you'll have to add code on both the server and client side to handle them.
DECLARE
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT(my_exception, -20001);
BEGIN
raise my_exception; -- message is default "ORA-20001: "
END;
/
BEGIN
raise_application_error(-20001, 'My custom error message'); -- message is "ORA-20001: My custom error message"
END;
/
This is often useful when you want to give a more helpful, descriptive error message.
DECLARE
age NUMBER;
BEGIN
age := 'hello';
EXCEPTION when VALUE_ERROR then
raise_application_error(-20001, 'Age must be a number.');
END;
/
So instead of a generic "PL/SQL: numeric or value error: character to number conversion error", we can tell the user exactly what the problem is: "Age must be a number".
A user-defined exception doesn't provide any message to the calling application. It'll just get a generic 'ORA-06510: PL/SQL: unhandled user-defined exception'. raise_application_error lets you pass a message describing the actual problem.
declare
out_of_stock exception;
begin
raise out_of_stock;
end;
/
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at line 4
begin
raise_application_error(-20000, 'Cheese is out of stock');
end;
/
ERROR at line 1:
ORA-20000: Cheese is out of stock
ORA-06512: at line 2
pragma exception_init(exception_name, error_code) lets you associate your own user-defined exception with a system error code. This can be useful from a programming perspective, but ultimately it doesn't add any value for the caller:
declare
invalid_day_of_month exception;
pragma exception_init(invalid_day_of_month, -1847);
d date;
begin
d := to_date('99-JAN-2020','DD-MON-YYYY');
exception
-- Pointless exception handler - just to demo raising a user-defined exception
when invalid_day_of_month then raise;
end;
/
ERROR at line 2:
ORA-01847: day of month must be between 1 and last day of month
ORA-06512: at line 10
ORA-06512: at line 7
Passing success/failure status via OUT parameters is usually a bad idea because the procedure will appear to have completed successfully, and the caller has to read the status to see whether it really succeeded or not.
This is my Department table:
create table Department(
DeptNo int primary key,
DeptName varchar2(21) not null,
DeptLocation varchar2(13) not null
);
I am trying to insert value for DeptName column with length more than accepted i.e. 21, which means I should get "VALUE_ERROR" exception.
The plsql code I am running is:
begin
insert into department values(1, 'Some random department name', 'SomeLocation');
exception
when value_error then
dbms_output.put_line('Cannot store the value!');
end;
As I am trying to catch the exception, it is not getting caught. I am getting the error:
ORA-12899: value too large for column "SQL_ZIRHWMLFPCEAKPGYGJJZLSIFI"."DEPARTMENT"."DEPTNAME" (actual: 27, maximum: 21) ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721
But if I change my exception from "value_error" to "others"
begin
insert into department values(1, 'Some random department name', 'SomeLocation');
exception
when others then
dbms_output.put_line('Cannot store the value!');
end;
then I get the expected output
Cannot store the value!
Where could I have gone wrong? Please let me know. Thanks!
PS: I am running all the code on livesql.oracle.com
Ideally, what you are using is correct and it should had worked as desired. But the exception VALUE_ERROR behaves differently in somecases. See the below illustrative example.
As per the documentation value_error comes when there is an
arithmetic, conversion, truncation, or size-constraint error occurs.
For example, when your program selects a column value into a character
variable, if the value is longer than the declared length of the
variable, PL/SQL aborts the assignment and raises VALUE_ERROR. In
procedural statements, VALUE_ERROR is raised if the conversion of a
character string into a number fails.
The last line says, In procedural statements, VALUE_ERROR is raised if the conversion of a character string into a number fails., but when i run this in a block it rasied INVALID_NUMBER exception.
SQL> declare
2 n number;
3 begin
4 select to_number('a')
5 into n
6 from dual
7 ;
8 exception
9 when value_error
10 then
11 dbms_output.put_line ('Value Error');
12 when invalid_number
13 then
14 dbms_output.put_line ('Invalid Number');
15 end;
16 /
Invalid Number
PL/SQL procedure successfully completed.
I expected that it would raise the VALUE_ERROR but it didn't. So it might be the case that Oracle was not able to raise value_error in your case and when you used WHEN OTHERS it was caught.
Edit:
Ok. So is it possible somehow to catch value_error exception by giving
the value longer than the declared length of the variable?
Explicit Raise of System Exception : Not very elegant but you can do it as below.
declare
var int;
var1 varchar2(21);
var2 varchar2(13);
begin
var1:='Some random department name';
var2:= 'SomeLocation'
If var1 > 21 then
RAISE VALUE_ERROR;
END IF;
If var2 > 13 then
RAISE VALUE_ERROR;
END IF;
insert into department values(1, var1, var2);
exception
when value_error then
dbms_output.put_line('Cannot store the value!');
end;
VALUE_ERROR was not the exception raised when you ran your code, if you want, you can defined an EXCEPTION and catch it, see below code for sample,
DECLARE
ORA_12899 EXCEPTION;
PRAGMA EXCEPTION_INIT(ORA_12899, -12899);
begin
insert into department values(1, 'Some random department name', 'SomeLocation');
exception
when ORA_12899 then
dbms_output.put_line('Cannot store the value!');
end;
/
Below code will have a VALUE_ERROR;
DECLARE
ORA_12899 EXCEPTION;
PRAGMA EXCEPTION_INIT(ORA_12899, -12899);
v_dept VARCHAR2(20);
begin
v_dept := 'Some random department name';
insert into department values(1, v_dept, 'SomeLocation');
exception
when ORA_12899 then
dbms_output.put_line('Cannot store the value!');
end;
/
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.
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
Suppose I have a PL/SQL function that selects one value from a table. If the query returns no records, I wish for the NO_DATA_FOUND error to propagate (so that the calling code can catch it), but with a more meaningful error message when SQLERRM is called.
Here is an example of what I am trying to accomplish:
FUNCTION fetch_customer_id(customer_name VARCHAR2) RETURN NUMBER;
customer_id NUMBER;
BEGIN
SELECT customer_id
INTO customer_id
FROM CUSTOMERS
WHERE customer_name = fetch_customer_id.customer_name;
RETURN customer_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
meaningful_error_message := 'Customer named ' || customer_name || ' does not exist';
RAISE;
END;
Is there a way to associate meaningful_error_message with the NO_DATA_FOUND error?
Update: It has been suggested that I use RAISE_APPLICATION_ERROR to raise a custom error code when NO_DATA_FOUND is encountered. The purpose of this question was to determine if this technique could be avoided so that the calling code can catch NO_DATA_FOUND errors rather than a custom error code. Catching NO_DATA_FOUND seems more semantically correct, but I could be wrong.
Use RAISE_APPLICATION_ERROR (-20001, 'your message');
This will return an error number -20001, and your message instead of the NO_DATA_FOUND message. Oracle has reserved the error numbers between -20001 and -210000 for user use in their applications, so you won't be hiding another Oracle error by using these numbers.
EDIT: RAISE_APPLICATION_ERROR is specifically designed to allow you to create your own error messages. So Oracle does not have another method of allowing dynamic error messages. To further refine this you can define your own exception in the package where you define your procedure. Add the following:
CUSTOMER_NO_DATA_FOUND EXCEPTION;
EXCEPTION_INIT (CUSTOMER_NO_DATA_FOUND, -20001);
In your procedure code, you do the RAISE_APPLICATION_ERROR, and the client code can do a
WHEN CUSTOMER_NO_DATA_FOUND THEN which looks better, and they still have the error message captured in SQLERRM.
As suggested by Thomas you can use RAISE_APPLICATION_ERROR. If you also want to keep the NO_DATA_FOUND error on the error stack you can add TRUE as a third parameter to the function:
DECLARE
l NUMBER;
BEGIN
SELECT NULL INTO l FROM dual WHERE 1 = 2;
EXCEPTION
WHEN no_data_found THEN
raise_application_error(-20001, 'Meaningful Message', TRUE);
END;
ORA-20001: Meaningful Message
ORA-06512: at line 8
ORA-01403: no data found (*)
The line tagged (*) is the original error message.