A procedure calls a function. It works with several tables, so I devided it to blocks. All blocks has its own exception section where the specific error message can be sent via e-mail.
Now I want to make the code cleaner and pass the exception in this order:
Block -> Its Function -> Caller Procedure
I can do it with 'raise' but in this case the caller procedure doesn't know where the exception came from.
Another solution I think is that the first block would use raise_application_error(User-Error-Id, 'Specific error message'). But in this case the original SQLCODE is hidden by raise_application_error.
How can I solve this?
I have something like this:
procedure main
...
begin
v_pcs := calc_pcs(parameters);
exception
When others then
dbms_output.put_line(SCLCODE || ' - ' || SQLERRM);
-- Here I'd like to read the part of the code where the exception happened
end;
funcion calc_pcs(
parameters
) as
begin
-- first block
begin
v_return := 5 / 0; -- just an example of an error
exception
when others then
raise -- How to add text to predefined exception. For example 'FirstBlock || SqlErrM'
end;
...
rest of the code contains additional blocks with their exception handling and
there is other code without blocks
...
return v_return;
Exception
when others then
raise;
end;
Thanks Sayan! Sorry, but I may have missed something important in my previous post, let me clarify.
I want to concatenate a placeholder text to the original predefined exception string of the start block, for example "First Block" || "divisor is equal to zero". Further exception handling would pass this along with the SQLCODE unchanged.
You can do like this:
declare
e1 exception;
e2 exception;
e3 exception;
pragma exception_init(e1, -20201);
pragma exception_init(e2, -20202);
pragma exception_init(e3, -20203);
procedure func ( x int, txt varchar2 ) is
begin
case x
when 1 then raise_application_error(-20201, 'x = ['||x||'] error text 1: ' || txt);
when 2 then raise_application_error(-20202, 'x = ['||x||'] error text 2: ' || txt);
when 3 then raise_application_error(-20203, 'x = ['||x||'] error text 3: ' || txt);
else null;
end case;
end func;
begin -- main block:
func(2, 'test');
exception
when e1 then
dbms_output.put_line('e1');
dbms_output.put_line(SQLCODE || ' - ' || SQLERRM);
when e2 then
dbms_output.put_line('e2');
dbms_output.put_line(SQLCODE || ' - ' || SQLERRM);
when e3 then
dbms_output.put_line('e3');
dbms_output.put_line(SQLCODE || ' - ' || SQLERRM);
end;
/
DBFiddle: https://dbfiddle.uk/s7HeZpNB
dbms_output:
e2
-20202 - ORA-20202: x = [2] error text 2: test
Related
My stored-procedure is looping executing different statements. I want to handle the following situations:
When the statement returns nothing (no_data_found), I want to quietly skip the rest of the loop (continue).
When the statement causes an error of any type, I want to report it, and then skip the rest of the loop (continue);
When the statement finds rows, I want to report it.
The code looks like:
...
LOOP
stmt := 'select * ......';
BEGIN
EXECUTE IMMEDIATE stmt;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
WHEN OTHERS THEN
dbms_out.put_line(stmt || ': ' || SQLCODE);
CONTINUE;
END;
dbms_out.put_line('Found! Use: ' || stmt);
END LOOP;
The above elicits no errors, but the Found-line is printed for every loop-iteration, including for statements, that yield no results...
Why is the CONTINUE-directive ignored -- am I wrong expecting the directive to be obeyed for any exception -- be it NO_DATA_FOUND or anything else?
In your exception block, the action for your NO_DATA_FOUND handler is NULL - so it executes the NULL statement (i.e. does nothing) and falls out of the BEGIN-END block, hitting the dbms_out.put_line('Found! Use: ' || stmt); statement. The only handler which will execute CONTINUE; is the WHEN OTHERS.
One way to get the behavior you describe is to do a SELECT COUNT(*)... into a numeric variable and then just check to see how many rows are returned:
DECLARE
csr SYS_REFCURSOR;
nCount NUMBER;
BEGIN
LOOP
stmt := 'SELECT COUNT(*) FROM (SELECT * from ... WHERE ...)';
OPEN csr FOR stmt;
FETCH csr INTO nCount;
CLOSE csr;
IF nCount > 0 THEN
dbms_out.put_line('Found! Use: ' || stmt);
ELSE
dbms_out.put_line(stmt || ': ' || SQLCODE);
END IF;
END LOOP;
END;
Of course this is not really valid as there's no way for the value of stmt to change, but I suspect your "real" code handles that.
I am fairly new to PL/SQL and am trying to implement some exception handling for a package I have written.
I have come across a situation where I have multiple exceptions that may be raised.
In the event these exceptions are raised I may want to do something specific for each of the exceptions, for example if exception A is raised then close and delete a file, but if exception B is raised then I need only close a cursor and send an email to warn someone that a fatal error has occurred.
This is fine and I understand how to use WHEN <EXCEPTION_NAME> THEN.
The problem I am having is that I can't write generic code which occurs for any exception raised to do something I will always want done in the event of an exception, for example writing to the log file. This means that I have to duplicate lines of code for each exception type as shown below.
DECLARE
test_exception EXCEPTION;
BEGIN
--some code
RAISE test_exception;
--some code
EXCEPTION
WHEN test_exception THEN
SEND_EMAIL('Something went wrong');
WRITE_TO_LOG('Error ' || SQLCODE || ' | ' || SUBSTR(SQLERRM, 1, 200) || ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
WHEN OTHERS THEN
SOME_OTHER_FUNCTION();
WRITE_TO_LOG('Error ' || SQLCODE || ' | ' || SUBSTR(SQLERRM, 1, 200) || ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
What I would like to achieve is something similar to this, which does not compile.
DECLARE
test_exception EXCEPTION;
BEGIN
--some code
RAISE test_exception;
--some code
EXCEPTION
WRITE_TO_LOG('Error ' || SQLCODE || ' | ' || SUBSTR(SQLERRM, 1, 200) || ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
WHEN test_exception THEN
SEND_EMAIL('Something went wrong');
WHEN OTHERS THEN
SOME_OTHER_FUNCTION();
END;
Obviously this example isn't complete but gives a rough idea. For a single duplicated line like this it isn't a problem, but if there are many cursors or files to close or other housekeeping this seems a bit verbose/tedious.
Is there a WHEN ALL THEN clause or similar? Would this be a use case for some kind of GOTO? Or am I trying to apply the wrong sort of concept here.
I can't seem to find people asking the same question which to me means I am approaching the issue in the wrong way or I am missing some basic knowledge.
Thanks
PL/SQL will only execute one exception block. So if you have some generic code (e.g. logging) you want to run for every exception, you have to it do in when others.
Then check the sqlcode to do exception specific handling. For example:
begin
raise TOO_MANY_ROWS;
exception
when others then
dbms_output.put_line ( 'Generic stuff' );
case sqlcode
when -1422 then
dbms_output.put_line ( 'TMR specific' );
when -1476 then
dbms_output.put_line ( 'ZD specific' );
else
null;
end case;
raise;
end;
/
Generic stuff
TMR specific
ORA-01422: exact fetch returns more than requested number of rows
begin
raise ZERO_DIVIDE;
exception
when others then
dbms_output.put_line ( 'Generic stuff' );
case sqlcode
when -1422 then
dbms_output.put_line ( 'TMR specific' );
when -1476 then
dbms_output.put_line ( 'ZD specific' );
else
null;
end case;
raise;
end;
/
Generic stuff
ZD specific
ORA-01476: divisor is equal to zero
Now, whether this is a good idea is debatable.
when others exception blocks should re-raise the exception in some way. This to prevent you from suppressing serious unexpected errors (like out of disk space). But for some specific exceptions you may want to carry on processing.
Addendum
If you have user-defined exceptions, you need to do a bit of extra work if you wand to handle these. One way to do it is to create a standard exceptions package. In it, name, initialize, and define value constants for all the exceptions you'll use.
You can then use these definitions for user-defined exceptions:
create or replace package excepts as
user_defined exception ;
user_defined_val pls_integer := -20001;
pragma exception_init ( user_defined, -20001 );
end;
/
begin
raise excepts.USER_DEFINED;
exception
when others then
dbms_output.put_line ( 'Generic stuff' );
case sqlcode
when -1422 then
dbms_output.put_line ( 'TMR specific' );
when -1476 then
dbms_output.put_line ( 'ZD specific' );
when excepts.user_defined_val then
dbms_output.put_line ( 'User-defined specific' );
else
null;
end case;
raise;
end;
/
Generic stuff
User-defined specific
ORA-20001:
If I'am undestanding correctly - solution is very simple. When you raising an exception, you can handle it in nested block and pass to the outer block by RAISE
DECLARE
test_exception EXCEPTION;
begin
RAISE test_exception;
--some code
EXCEPTION
WHEN OTHERS THEN
BEGIN
dbms_output.put_line( 'WRITE_TO_LOG');
RAISE;
EXCEPTION
WHEN test_exception THEN
dbms_output.put_line( 'test_exception');
WHEN OTHERS THEN
dbms_output.put_line( 'some other function');
end;
END;
output:
WRITE_TO_LOG
send mail
We are using FORALL.....SAVE EXCEPTIONS. At the end of the loop, we have this:
FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP<BR><BR>
DBMS_OUTPUT.PUT_LINE('ERROR CREATING STAGING TICKER: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));<BR><BR>
DBMS_OUTPUT.PUT_LINE('INDEX INFO: ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);<BR>
END LOOP;
Is there any way for me to get at actual VALUES in that array? Say if a customers email was too long.....for me to actually display the value which caused the error? Rather than just some index number?
Thanks!
You can use the loop variable i to display the content of the exception array in your case. See below an example procedure:
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 * 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
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;
/
Ouput:
SQL> DECLARE
empid DBMS_SQL.NUMBER_TABLE;
BEGIN
empid (1) := 1;
empid (2) := 9;
PROC1 (empid);
END;
/
exception Raised for record 1
PL/SQL procedure successfully completed.
This is a best practice question on error handling through multiple levels of PL/SQL procedures. I've looked at a few other questions to help me out, in particular this one.
Currently, I have a program with Procedure 1, which calls Procedure 2, which calls Procedure 3. I'm trying to perform adequate error handling - but I'd like to output eventually the exact problem back to the application layer. I'm hoping to get some ideas on how I can do this efficiently and clearly.
My current solution method is below, but it seems rather messy to me, with lots of variable declarations. I am very new to PL/SQL (and SQL in general) so I'd appreciate any advice on:
Good error handling techniques when dealing with multiple layers of procedures.
Feeding error messages back up to application layer (in my procedure below, represented by "out_overall_output" variable.
Program Flow: UI -> Proc 1 -> Proc 2 -> Proc 3
Procedure 1:
--One input variable, one output.
in_id VARCHAR2;
out_overall_output VARCHAR2;
...
DECLARE
l_success BOOLEAN;
l_error_output VARCHAR2(100);
BEGIN
Proc2(id, l_success, l_error_output);
IF l_success = FALSE THEN
out_overall_output = l_error_output
END IF
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
out_overall_output:= 'Error calling Proc 2'
RETURN;
END;
--Normal flow continues if l_success is true...
Procedure 2:
-- One input variable, two output.
in_id
out_success
out_error_output
//other logic
DECLARE
l_success BOOLEAN;
l_error_output VARCHAR2(100)
BEGIN
Proc3(id, l_success, l_error_output)
IF l_success = FALSE THEN
out_error_output = l_error_output
END IF
EXCEPTION
WHEN OTHERS
out_error_output = 'Error calling Proc 3'
RETURN;
END;
Procedure 3:
--One input variable, two output.
in_id VARCHAR2;
out_success BOOLEAN;
out_error_message VARCHAR2;
...
BEGIN
DELETE
FROM table
WHERE id = in_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
out_success = FALSE;
out_error_message = 'Error - No data to delete'
WHEN OTHERS THEN
out_success = FALSE;
out_error_message = 'Error deleting data.'
END;
Note: The levels of procedure calling goes deeper than this. The snippets I have shown are greatly simplified. The error messages and variable names in my real procedures are more descriptive.
To show exact explanations of "what happens with a server" for application level you can try following. In procedures:
create or replace procedure p1 is
...
exception
when <some_error> then
<do something>
-- re-raise error:
raise_application_error(-20001, 'Client with ID '|| ID || ' has no right to perform action "' || ACTION_NAME || '"', true);
end;
create or replace procedure p2 is
begin
p1;
exception
when <another_error> then
<do something>
-- re-raise error:
raise_application_error(-20002, 'Action "' || ACTION_NAME || '" is not completed', true);
end;
create or replace procedure p3 is
begin
p2;
exception
when <another_error> then
<do something>
-- re-raise error:
raise_application_error(-20003, 'Purchasing of "' || CAR_NAME || '" cancelled', true);
end;
And in top level procedure:
create or replace procedure top_level_procedure is
begin
p1;
exception
when <one_more_error> then
<do something>
raise_application_error(-20004, dbms_utility.format_error_backtrace);
end;
After exception in p1 you will see something like this:
ORA-20003: Purchasing of "Cool red Ferrari" cancelled
ORA-20002: Action "car purchase" is not completed
ORA-20001: Client with ID 123 has no right to perform action "Spent all money of Bill Gates"
Third parameter of procedure raise_application_error with false value cuts all previous error messages. If you will use false value in procedure p3, you will see only one error message with code ORA-20003 in this example.
P. S. Also you can define your own exceptions and use them in WHEN .. THEN clause. Here you find more information and examples: https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/errors.htm#LNPLS00704
P. P. S. How to log. Log procedure:
create or replace procedure log(p_log_message varchar2) is
pragma autonomous_transaction;
begin
insert into log_table(..., log_message) values (..., p_log_message);
commit;
end;
Call log procedure:
when <one_more_error> then
<do something>
log(..., dbms_utility.format_error_backtrace);
raise_application_error(-20004, dbms_utility.format_error_backtrace);
In our Stored procedures we have the following code towards the very end.
<<SQL_ERROR>>
V_SYS_ERROR_MSG := SUBSTR(SQLERRM, 1, 252);
DBMS_OUTPUT.PUT_LINE('ERROR IN EXECUTION IN PROCEDURE');
DBMS_OUTPUT.PUT_LINE('THE ERROR CODE IS ' || V_SYS_ERROR || '- ' ||
V_SYS_ERROR_MSG);
we have statements like following which call the error block.
IF V_SYS_ERROR <> 0 THEN
GOTO SQL_ERROR;
the DBMS output statements come even when there is no error. How can we avoid this?
I am not recommending this GOTO approach: as others have already said, exceptions are the correct way to handle errors in PL/SQL. But to address your specific question, you could do this:
BEGIN
IF V_SYS_ERROR <> 0 THEN
GOTO SQL_ERROR;
END IF;
GOTO PROC_END;
<<SQL_ERROR>>
V_SYS_ERROR_MSG := SUBSTR(SQLERRM, 1, 252);
DBMS_OUTPUT.PUT_LINE('ERROR IN EXECUTION IN PROCEDURE');
DBMS_OUTPUT.PUT_LINE('THE ERROR CODE IS ' || V_SYS_ERROR || '- ' ||
V_SYS_ERROR_MSG);
<<PROC_END>>
NULL;
END;
Of course, this still involves changing the code, so if you are doing that why not do it properly anyway? i.e.
DECLARE
SQL_ERROR EXCEPTION;
BEGIN
IF V_SYS_ERROR <> 0 THEN
RAISE SQL_ERROR;
END IF;
EXCEPTION
WHEN SQL_ERROR THEN
V_SYS_ERROR_MSG := SUBSTR(SQLERRM, 1, 252);
DBMS_OUTPUT.PUT_LINE('ERROR IN EXECUTION IN PROCEDURE');
DBMS_OUTPUT.PUT_LINE('THE ERROR CODE IS ' || V_SYS_ERROR || '- ' ||
V_SYS_ERROR_MSG);
RAISE;
END;
By the way, DBMS_OUTPUT.PUT_LINE is not suitable for output of error messages in a production application system. Only use it for debugging during development.
you should avoid GOTO statements, they are messy as you have noticed. PL/SQL comes with error handling, you should use the EXCEPTION synthax to deal with errors:
BEGIN
<code goes here>
EXCEPTION
WHEN <exception> THEN
<deal_with_it>
WHEN OTHERS THEN
<log_error>
RAISE;
END;
I don't think you'll get the results you want without using an exception handler. From the PL/SQL User's Guide:
SQLERRM with no argument is useful
only in an exception handler. Outside
a handler, SQLERRM with no argument
always returns the normal, successful
completion message.
So first of all, it is possible that an error is happening but you are seeing a message saying "normal, successful completion" because that is what SQLERRM will always return in this context.
Assuming that's not the case, it sounds like you are simply allowing control to flow from the "normal" code into the "error handler". If the error handler is at the very end of the procedure, then a simple fix would be to add a RETURN statement just before the <> label.
A better fix would be to move the error-handler code to a separate procedure and call that procedure instead of using a GOTO.