Suppose I have 3 procedures running together, which insert record A,B,C respectively:
exec P1
exec P2
exec P3
Assume P2 will throw exception, while P1 and P3 are okay.
My expected flow is :
Run P1 -> A inserted -> Run P2 -> Encounter exception -> Rollback -> Stop from running P3
The ultimate expected outcome is nothing changed.
I have defined exception and exception handling for P2, which will rollback. However, I cannot stop it from running P3, i.e. C is still inserted.
Google tells me there is WHENEVER SQLERROR EXIT ROLLBACK, but I discovered that it could not work on self-defined exception. In other words, it only works on Oracle official exception, such as:
ERROR at line 2:
ORA-06550: line 2, column 10:
PLS-00201: identifier 'COLUMN_DOES_NOT_EXIST' must be declared
ORA-06550: line 2, column 3:
PL/SQL: SQL Statement ignored
To achieve the expected outcome, my thoughts are(but I don't know how to do it!):
Add exception handling to P2, such that it can stop P3 from running.
Run WHENEVER SQLERROR EXIT ROLLBACK before running the procedures. (but it can only work on Oracle official exception instead of my defined exception)
I do appreciate your help if you can fix either one.
The following is my code for your reference:
PROCEDURE INSERT(
INPUT IN TABLE.INPUT%TYPE DEFAULT NULL
) AS
BEGIN
IF INPUT NOT IN ('A','B','C') THEN
RAISE invalid_input;
END IF;
INSERT INTO (...) VALUES (...);
EXCEPTION
WHEN invalid_input THEN
DBMS_OUTPUT.PUT_LINE ('Invalid input' );
rollback; ---How to stop next procedure from running?
WHEN others THEN
rollback;
END;
When I run
exec INSERT(A)
exec INSERT(Y)
exec INSERT(C),
the outcome is that C is inserted, while I want nothing changed.
I added one line to your procedure (and changed a bit to run test example):
create or replace PROCEDURE ins(P_INPUT IN t.INPUT%TYPE DEFAULT NULL) AS
invalid_input exception;
BEGIN
IF P_INPUT NOT IN ('A','B','C') THEN
RAISE invalid_input;
END IF;
INSERT INTO t (INPUT) VALUES (P_INPUT);
EXCEPTION
WHEN invalid_input THEN
DBMS_OUTPUT.PUT_LINE ('Invalid input' );
rollback; ---How to stop next procedure from running?
raise; -- re-raise exception added
WHEN others THEN
rollback;
END;
Table:
create table t (input varchar2(1));
Test script:
SQL> declare
total_rows number;
begin
ins('A');
ins('D');
ins('C');
exception
when others then
select count(*) into total_rows from t;
dbms_output.put_line('Error ocured. Total rows in table T: ' || total_rows);
end;
/
Invalid input
Error ocured. Total rows in table T: 0
PL/SQL procedure successfully completed.
As I understand, it is your desired result.
Related
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>
This is my procedure:
create or replace procedure p1(p_deptno in number)
is
cursor c is select * from emp where deptno=p_deptno;
i emp%rowtype;
begin
open c;
loop
fetch c into i;
exit when c%notfound;
dbms_output.put_line(i.ename);
end loop;
exception
when no_data_found then
dbms_output.put_line('Give proper deptno');
end p1;
/
When I run it using SQL*Plus, I get this:
SQL> exec p1(70);
PL/SQL procedure successfully completed.
But deptno 70 is not available. Exception should be raised, but it is not. What am I doing wrong?
Using RAISE_APPLICATION_ERROR Will be more appropriate here if we need to show a user defined error for this particular error message.
CREATE OR REPLACE PROCEDURE p1(
p_deptno IN NUMBER)
IS
i emp%rowtype;
BEGIN
SELECT * INTO i FROM emp WHERE deptno=p_deptno;
EXCEPTION
WHEN no_data_found THEN
RAISE_APPLICATION_ERROR(-20001,'Dept no '||p_deptno||' has no data',TRUE);
END p1;
No data found exception would be raised only if you fire a select (not a cursor). Following is a sample based on your code where this would happen:
create or replace procedure p1(p_deptno in number)
is
i emp%rowtype;
begin
select * into i
from emp where deptno=p_deptno;
exception
when no_data_found then
dbms_output.put_line('Give proper deptno');
end p1;
/
You could raise an exception based on %NOTFOUND as demonstrated by Gavin.
Also,the first fetch from an open cursor, cursor_name%NOTFOUND returns NULL.Thereafter, it returns FALSE if the last fetch returned a row, or TRUE if the last fetch failed to return a row. (https://docs.oracle.com/cd/B12037_01/appdev.101/b10807/13_elems011.htm)
You need to call RAISE (documentation link here)
IF i IS NULL THEN
RAISE no_data_found;
END IF;
SQL> Exec Dbms_Scheduler.stop_job('US_ALERT',true);
begin Dbms_Scheduler.stop_job('US_ALERT',true); end;
ORA-27466: internal scheduler error: 1870
ORA-06512: at "SYS.DBMS_ISCHED", line 227
ORA-06512: at "SYS.DBMS_SCHEDULER", line 674
ORA-06512: at line 1
Does anyone has any idea of what's going wrong here ?
Database : Oracle
Version : 12c
This error is caused by the stack being unwound by unhandled exceptions in pl/SQL code. Most likely the scheduler is executing a procedure with some bad code in it. (See http://www.techonthenet.com/oracle/errors/ora06512.php)
The first thing to do is drop the job
BEGIN
DBMS_SCHEDULER.DROP_JOB('myjob1');
END;
/
Then start investigating your stored procedure(s) and add in some exception handling.
For example you might have some pl/sql code as follows
DECLARE pe_ratio NUMBER(3,1);
BEGIN
SELECT price / earnings INTO pe_ratio FROM stocks WHERE symbol = 'XYZ'; -- might cause division-by-zero error
INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio); COMMIT;
End;
If this was called by your job it could result in a ora 06512. (You probably will need to add in some logging/traces into to your pl/SQL to narrow this down, the line numbers in the error messages you reported may also help)
Replace it with
DECLARE pe_ratio NUMBER(3,1);
BEGIN
SELECT price / earnings INTO pe_ratio FROM stocks WHERE symbol = 'XYZ'; -- might cause division-by-zero error
INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio);
COMMIT;
EXCEPTION -- exception handlers begin
WHEN ZERO_DIVIDE THEN -- handles 'division by zero' error
INSERT INTO stats (symbol, ratio) VALUES ('XYZ', NULL);
COMMIT; ...
WHEN OTHERS THEN -- handles all other errors
ROLLBACK;
END; -- exception handlers and block end here
No more unhandled exceptions should result in no more ora 06512 errors and therefore your job will stop when when requested.
Here's some good info on exception handling in oracle
https://docs.oracle.com/cd/A97630_01/appdev.920/a96624/07_errs.htm
Hope that helps.
I have a procedure created and i am using a merge query inside it.
when i compile the procedure, there is no error and when i try to execute it giving me
below error. Can someone help in solving this.
Code:
CREATE OR REPLACE PROCEDURE DEVICE.Check1
IS
BEGIN
MERGE INTO DEVICE.APP_C_CATEGORY A
USING (SELECT market_segment_id,
market_segment_name,
UPDATE_USER,
UPDATE_DATE
FROM CUST_INTEL.MSE_MARKET_SEGMENT_MASTER#SOURCE_CUST_INTEL.ITG.TI.COM
WHERE market_segment_id NOT IN ('120', '130', '100')) B
ON (A.APP_CATEGORY_ID = B.market_segment_id
AND A.APP_CATEGORY_NAME = B.market_segment_name)
WHEN MATCHED
THEN
UPDATE SET A.DESCRIPTION = B.market_segment_name,
A.PARENT_APP_AREA_ID = NULL,
A.RECORD_CHANGED_BY = B.UPDATE_USER,
A.RECORD_CHANGE_DATE = B.UPDATE_DATE
WHEN NOT MATCHED
THEN
INSERT (A.APP_CATEGORY_NAME,
A.DESCRIPTION,
A.TYPE,
A.PARENT_APP_AREA_ID,
A.RECORD_CHANGED_BY,
A.RECORD_CHANGE_DATE)
VALUES (B.market_segment_name,
B.market_segment_name,
1,
NULL,
B.UPDATE_USER,
B.UPDATE_DATE);
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE (SQLERRM);
DBMS_OUTPUT.PUT_LINE (SQLCODE);
END;
/
Error: when i am executing
BEGIN
DEVICE.CHECK1;
COMMIT;
END;
the following errors occur:
ORA-06550: line 2, column 10:
PLS-00302: component 'CHECK1' must be declared
ORA-06550: line 2, column 3:
PL/SQL: Statement ignored
This is nothing to do with the merge, it isn't getting as far as actually executing your procedure. From the create procedure statement you have a schema called DEVICE. Since the error message is only complaining about CHECK1, not DEVICE.CHECK1, you also appear to have a package called DEVICE. Your anonymous block is trying to find a procedure called CHECK1 within that package, not at schema level.
If you are connected as the device schema owner (user) when you execute this, just remove the schema prefix:
BEGIN
CHECK1;
COMMIT;
END;
/
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);