In Oracle procedure I call other package's procedure. And in logs I see external procedure is called and writed own initial log. But in some cases this procedure not ends properly. How I can handle this situation? In the following example 'END' or 'ERROR' not logged for such situations
procedure AAA is
begin
Log('started');
CallOtherPackagesProcedure();
Log('END');
exception
when others then
LOG('ERROR', SQLERRM || ';' || dbms_utility.format_error_backtrace);
raise;
end;
Related
I have a problem running my PL/SQL script in SQL*Plus. I can run SQL commands normally but when I want to a run any PL/SQL code it gives nothing. See code and output below.
DECLARE
x_salary employee.salary%TYPE;
BEGIN
select salary
into x_salary
from employee
where ssn=&enter_ssn;
--Output the result
DBMS_OUTPUT.PUT_LINE('Salary is ' || x_salary);
EXCEPTION
--Output when no records are returned
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE ('No employee found');
WHEN others THEN
DBMS_OUTPUT.PUT_LINE ('Error encountered, but cause unknown');
END;
PL/SQL procedures needs / after procedure definition under sqlplus
DECLARE
...
BEGIN
...
END;
/
Put a slash / in a new line after END; in your script.
From the documentation:
You must include a semicolon at the end of each SQL command and a slash (/) on a line by itself after each PL/SQL block in the file.
Then execute the SQL file in SQL*Plus command line as:
#C:\your_script.sql;
Exception
WHEN OTHERS THEN
--dbms_output.put_line('pl_update_sidm_user r: ERROR CODE:' || sqlcode || '~' ||
--sqlerrm || ' EMPL_NBR:' || r.EMPL_NBR);
insert into ERROR_MSG (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL, 'pl_update_sidm_user_duty_role r2');
END;
I would like to put the error result to a table.
However, how can I do that?
Can I put the result of dbms_output to a table as a string?
If not, can I get the sqlcode,sqlerrm without using dbms_output?
Thank you !!
From the documentation,
A SQL statement cannot invoke SQLCODE or SQLERRM. To use their values
in a SQL statement, assign them to local variables first
Also,
Oracle recommends using DBMS_UTILITY.FORMAT_ERROR_STACK except when using the FORALL statement with its SAVE EXCEPTIONS clause
So, for SQLCODE or SQLERRM, you should assign them into variables and use them.
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION
WHEN OTHERS THEN
v_errcode := SQLCODE;
v_errmsg := SQLERRM;
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) --change your table name
values (ERROR_MSG_ID_SEQ.NEXTVAL,
v_errcode||':'||v_errmsg);
END;
/
Preferably use insert like this instead, as per Oracle's recommendation.
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL,
DBMS_UTILITY.FORMAT_ERROR_STACK);
Demo
Technically what others are suggesting is correct: the "insert" operation executed in the "exception when others" block will actually insert a new row in the log table.
the problem is that such insert statement will be part of the same transaction of the main procedure and, since you had an error while executing it, you are very likely to rollback that transaction, and this will rollback also the insert in your log table
I suppose the problem you are facing is not that you aren't successfully logging the error message: it is that you are rolling it back immediately afterwards, along with all the other writes you did in the same transaction.
Oracle gives you a way of executing code in a SEPARATE transaction, by using "autonomous transaction" procedures.
you need to create such a procedure:
create or replace procedure Write_Error_log(
arg_error_code number,
arg_error_msg varchar2,
arg_error_backtrace varchar2) is
PRAGMA AUTONOMOUS_TRANSACTION;
begin
INSERT INTO error_msg (
error_msg_id,
error_code,
error_msg,
error_stack)
VALUES (
error_msg_id_seq.NEXTVAL,
arg_error_code,
arg_error_msg,
arg_error_backtrace);
commit; -- you have to commit or rollback always, before exiting a
-- pragma autonomous_transaction procedure
end;
What this procedure does is to write a new record in the log table using a totally separate and independent transaction: the data will stay in the log table even if you execute a roll back in your calling procedure. You can also use such a procedure to create a generic log (not only errors).
All you have to do now is to call the procedure above whenever you need to log something, so your code becomes:
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION WHEN OTHERS THEN
Write_Error_log(SQLCODE, SQLERRM, dbms_utility.format_error_backtrace);
END;
/
P.S: there might be some typos in my code: I can't test it right now since I can't reach an oracle server in this moment.
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);
I would like to know, how can I exit the execution when an error occurs. In Microsoft SQL Server there is a RETURN clause, which does the work. But I would like to know similar functionality in Oracle. I am using Oracle Sql Developer. Here is the script I am using:
First block throws error due to Unique Key Violation, even though it throws error the execution goes to next block and executes the insert statement. I want to end the execution or exit at first block of code itself.
Please help me to write the code.
First anonymous PL/SQL block:
set serveroutput on;
BEGIN
insert into test values(1);
insert into test values(1);
COMMIT;
dbms_output.put_line('PRINT SOMETHING 1');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
/
Second anonymous PL/SQL block:
set serveroutput on;
BEGIN
insert into test values(6);
COMMIT;
dbms_output.put_line('PRINT SOMETHING');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
/
If you create a stored procedure, you have more control and can exit whenever you like with a return statement.
So create a stored proc:
create or replace procedure myProc as
begin
dbms_ouput.put_line('i am here');
return;
dbms_ouput.put_line('and not here');
end;
Then in sqlplus or developer:
exec myProc();
You can nest the blocks into a single 'program unit'.
In this way an exception in the first block will stop the whole program unit from executing, rather than just being limited in scope to the first block.
set serveroutput on;
BEGIN
BEGIN
insert into test values(1);
insert into test values(1);
COMMIT;
dbms_output.put_line('PRINT SOMETHING 1');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
BEGIN
insert into test values(6);
COMMIT;
dbms_output.put_line('PRINT SOMETHING');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
END;
/
You should be able to use "exit" - see the Oracle documentation here: http://docs.oracle.com/cd/B19306_01/server.102/b14357/ch12023.htm
Note that this will end your SqlPlus session, but I don't know of another way of doing it aside from using a single block or stored procedure.
Another useful statement is:
WHENEVER SQLERROR EXIT SQL.SQLCODE
Oracle documentation: http://docs.oracle.com/cd/B19306_01/server.102/b14357/ch12052.htm
Thanks for your valuable comments.
JoshL, i tried using EXIT but i am ending up with error. Please correct my code( I am new to PL/SQL). "WHENEVER SQLERROR EXIT" is good to use but my issue is that I use these sql scriptsd in InstallShield, so InstallShield installers does not recognize these statements and throws error.
set serveroutput on;
BEGIN
insert into test values(1);
insert into test values(1);
COMMIT;
dbms_output.put_line('PRINT SOMETHING 1');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
exit;
end if;
END;
/
The EXIT command is only for use within a loop in PL/SQL. The EXIT command leaves the loop at that point. If you use the EXIT command outside a loop in PL/SQL the compiler throws an error.
The EXIT command in SQLPlus exits the SQLPlus session.
This is confusing, because they are two different Oracle products. SQL*Plus can run PL/SQL and the EXIT statement is a valid statement in both products, but with different contexts.
In my application oracle procedure is currently called through unix script, when there is error in exception block we are handling something like this :-
PROC_LOGS('<PROC_NAME>', <TABLE_NAME>, 'Exception','Exception occured - '||SQLERRM);
PROC_LOG does nothing but inserts entry into log table but we need now to catch that error in unix also (return some value except 0) from where is it called so remaining process can be terminated best on that, what is the best way to do this?
CREATE OR REPLACE....
...
DECLARE
....
BEGIN
..
...
EXCEPTION
WHEN OTHERS THEN
PROC_LOGS('<PROC_NAME>', <TABLE_NAME>, 'EXCEPTION','EXCEPTION OCCURED - '||SQLERRM);
END;
Unix script part from where I m calling procedure
sqlplus <<-!
$US/$P#$I
set serveroutput on
#$SQL/execute_proc.sql $1 $2
execue_proc contains something like this :-
define IN_1 = '&1';
define IN_2 = '&2';
spool $SQL/test_&&IN
declare
P_IN_TABLE_NAME varchar2(250) := '&&IN_TABLE_NAME';
P_IN_REGION varchar2(250) := '&&IN_REGION';
begin
PROC_UPDATE_CHARGE_FACT(P_IN_TABLE_NAME,P_IN_REGION);
end;
/
spool off
You need to raise the exception, like this:
EXCEPTION
WHEN OTHERS THEN
PROC_LOGS('<PROC_NAME>', <TABLE_NAME>, 'EXCEPTION','EXCEPTION OCCURED - '||SQLERRM);
RAISE;
END;
or use RAISE_APPLICATION_ERROR:
EXCEPTION
WHEN OTHERS THEN
PROC_LOGS('<PROC_NAME>', <TABLE_NAME>, 'EXCEPTION','EXCEPTION OCCURED - '||SQLERRM);
RAISE_APPLICATION_ERROR (-20002, 'An unexpected exception occurred.');
END;
You are presumably calling SQL*Plus, and SQL*Plus has a configuration item that should help.
http://docs.oracle.com/cd/E11882_01/server.112/e16604/ch_twelve052.htm#BACHCFEF
Since you probably execute the procedure from an anonymous block, the error should propagate to SQL*Plus and "WHENEVER SQLERROR EXIT SQL.SQLCODE" will return the relevant error code to the unix environment.
One way could be to have proc_log() to also write to a log file and then have the unix script to check the log file.