Which command raised the exception? - oracle

Is there a variable like SQLERRM or SQLCODE that holds the statement which raised the error?
example:
/*
if some error raised from this code
and I want to know which statement cause the failure..
I wish to use some oracle varaible to know it
*/
begin
select * from t1;
select * from t2;
exception when others
dbms_output.put_line(sqlerrm || ' raised from this statement:' || <some_variable>;
end;
-- excepted result: no data found raised from this statement: select * from t2

Simple answer, no. You're losing some information by defining an exception handler. With an unhandled exception you'd get an error message which includes the line number. But obviously we need to handle errors, log them, etc. So not having a line number is pretty rubbish.
Fortunately there are a couple of options. In older versions of Oracle we can use dbms_utility.format_error_backtrace() and dbms_utility.format_error_stack() to get some useful information, including the line numbers. It's pretty unwieldy and (especially for the backtrace) verbose.
In Oracle 12c we got a whole package devoted to PL/SQL call stack: UTL_CALL_STACK. It is a box of bits and requires more than one call to get things but we can retrieve a specific line number with unit_line(). Tim Hall has written a typically fine introduction to the new feature. Find out more.
The other thing to consider is how good program design can resolve this problem. Specifically the Single Responsibility Principle. This is a fundamental guideline of program design: a program unit should do one thing. If we asking the question "which command through this error" it can be a sign that we're violating the SRP.
Let's resign your code so it follows this design principle:
declare
type nt1 is table of t1%rowtype;
type nt2 is table of t2%rowtype;
l_t1 nt1;
l_t2 nt2;
x_t1_ndf exception;
x_t2_ndf exception;
function get_t1 return nt1 is
return_value nt1;
begin
select *
bulk collect into return_value
from t1;
if return_value.count() = 0 then
raise x_t1_ndf;
end if;
return return_value;
end get_t1;
function get_t2 return nt2 is
return_value nt2;
begin
select *
bulk collect into return_value
from t2;
if return_value.count() = 0 then
raise x_t2_ndf;
end if;
return return_value;
end get_t2;
begin
l_t1 := get_t1;
l_t2 := get_t2;
exception
when x_t1_ndf then
dbms_output.put_line('T1 has no data');
when x_t2_ndf then
dbms_output.put_line('T2 has no data');
end;
Obviously more typing than your original code but partly that's because this toy is complete working code, unlike the code you posted. Also in real life these modules would be discrete units, rather than private functions in an anonymous block, and so we could re-use them in multiple other programs.
Also dbms_output.put_line() is not the proper way to handle exceptions, but I've left that because it's what your code does.

There is no built-in that you can use for that.
One way could be by handling the exceptions of the single statements, with something like (pseudo-code):
declare
err varchar2(100);
myException exception;
begin
...
begin
select * from t1;
exception
when others then
err := 'Error in select * from t1: ' || sqlerrm;
raise myException
end;
begin
select * from t2;
exception
when others then
err := 'Error in select * from t2: ' || sqlerrm;
raise myException
end;
...
exception
when myException then
dbms_output.put_line(err);
when others then
dbms_output.put_line('Unhandled exception: ' || sqlerrm);
end;
For something more, this can be very useful.

Using a single exception handler for multiple statements always mask
the statement that caused an error.
Instead, you can use a Local variable(Locator) to track statement execution, as follows:
DECLARE
err_stmt NUMBER:= 1; -- Indicates 1st SELECT statement
BEGIN
SELECT ... -- Statement 1
err_stmt := 2; -- Indicates 2nd SELECT statement
SELECT ... -- Statement 2
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm || ' raised from this statement number:' || err_stmt;
END;
Cheers!!

Related

Catching multiple exception at once in oracle

Please let me know whether it is possible to catch multiple exception at same time in oracle. Not like 1 user defined and 1 is oracle default .I need to catch multiple user defined exception at same time . Kindly let me know how to do .
Thank you !
Certainly, there is - if I understood the question correctly. It is called WHEN OTHERS. Though, people usually misuse it, especially when they use
exception
when others then
null;
end;
as it successfully hides any errors that might appear. WHEN OTHERS is OK during development process, but might be really bad in production, especially if it doesn't contain raise.
Yes, you can do what you want from "When Others" as indicated by #Littlefoot or bulk processing errors (not covered here). But additionally you can have an OR condition exception name clause on the WHEN . It's not very useful as generally requires more code the 2 separate WHEN condition, but it is valid syntax. The following demonstrates various error definition methods and exception processing.
create table except_demo ( id integer, col1 varchar2(20));
insert into except_demo (id, col1)
select 5,'OK' from dual union all
select 6,'Too Many' from dual union all
select 6,'Still' from dual;
select id, count(*) from except_demo group by id;
create or replace procedure catcher(which_in integer, select_in boolean default False)
is
e_user_1 exception;
e_user_2 exception;
invalid_which_range exception;
appErr_inactive_acct exception;
sample_ora_error exception;
pragma exception_init (sample_ora_error, -00060);
rae exception;
rae_num constant integer := -20100;
pragma exception_init (rae, -20100);
col1_value except_demo.col1%type;
begin
dbms_output.put( 'Enter catcher(' || which_in || ') Result=>');
if which_in > 8
then raise invalid_which_range;
end if ;
if select_in
then
select col1
into col1_value
from except_demo
where id = which_in;
dbms_output.put_line('Select Successful 1 row selected.');
else
case which_in
when 1 then raise e_user_1;
when 2 then raise e_user_2;
when 3 then raise appErr_inactive_acct;
when 4 then raise sample_ora_error;
else raise_application_error(rae_num, 'Which_In=' || which_in || ' invalid. Please specify number 1-7 only');
end case;
end if;
exception
when e_user_1
then dbms_output.put_line('Error e_user_1'); -- user error
when e_user_2
then dbms_output.put_line('Error e_user_2');
when no_data_found or too_many_rows
then dbms_output.put_line('Select except_demo where id=' || which_in ||'; Returned 0 or more than 1 row. Must return exactly 1.' ); -- oracle predefined error
when sample_ora_error
then dbms_output.put_line('Ora Error:: ' || sqlerrm ); -- oracle error NOT predefined
when appErr_inactive_acct
then dbms_output.put_line('Error Account id ' || which_in || ' is inactive.'); -- user error
when rae
then dbms_output.put_line(sqlerrm);
end catcher;
declare
do_select boolean;
begin
for i in 1..9
loop
do_select := (i between 5 and 7);
catcher(i,do_select);
end loop;
exception
when others
then
dbms_output.put_line('Error returned from catcher=>' || sqlerrm);
raise;
end ;
drop procedure catcher;
drop table except_demo;
In a live environment the dbms_output statement would be replaced writing the message and other information to a exception log table and NOT dbms_output.
I have a very minor disagreement with Littlefoot. I firmly believe that what ever is written in development, whether intended or not, will run in production. Too often it is the unintended that gets you into trouble. Therefore the example of a misused WHEN OTHERS is invalid even in development.

Exception in procedure

I have procedure like below, but when block is run it does not shows message for error if data is not found.
CREATE OR REPLACE
PROCEDURE DDPROJ_SP
(P_IDPROJ IN DD_PROJECT.IDPROJ%TYPE,
P_INFO OUT VARCHAR2,
p_check OUT VARCHAR2)
IS
CURSOR cur_ddproj IS
SELECT *
FROM dd_project
WHERE idproj = p_idproj;
lv_projinfo_txt VARCHAR2(100);
BEGIN
FOR rec_ddproj IN cur_ddproj LOOP
lv_projinfo_txt := (rec_ddproj.idproj||', '||rec_ddproj.projname||
', '||rec_ddproj.projstartdate||', '||rec_ddproj. projenddate||
', '||rec_ddproj.projfundgoal||', '||rec_ddproj.p rojcoord);
END LOOP;
P_INFO := LV_PROJINFO_TXT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
P_CHECK :='Please select another project';
DBMS_OUTPUT.PUT_LINE(P_CHECK);
END;
And block:
DECLARE
LV_INFO_TXT VARCHAR2(100);
LV_CHECK_TXT VARCHAR2(30);
BEGIN
DDPROJ_SP(00,lv_info_txt,lv_check_txt);
DBMS_OUTPUT.PUT_LINE(LV_INFO_TXT);
END;
After RUNNING BLOCK IF id provided is correct I would receive requested information but if ID is not found exception will not show message on print.
Firstly, as has been pointed out, your exception handler doesn't do anything really visible except call dbms_output, the results of which you'll only see if you set serverout on or otherwise access the results from dbms_output.
Secondly and more importantly, when you use a FOR loop to process the results of a cursor, the NO_DATA_FOUND exception will never be raised.
If you want to detect no rows found, you have a few options:
After the loop, check if the variable was set, e.g.:
...
end loop;
if lv_projinfo_txt is null then
raise no_data_found;
end if;
If you don't expect more than 1 record to be found by the query (which is suggested by your predicate on an "id"), you can avoid the FOR loop and use a simple select into:
PROCEDURE DDPROJ_SP
(P_IDPROJ IN DD_PROJECT.IDPROJ%TYPE,
P_INFO OUT VARCHAR2,
p_check OUT VARCHAR2)
IS
rec_ddproj dd_project%rowtype;
lv_projinfo_txt VARCHAR2(100);
BEGIN
SELECT *
into rec_ddproj
FROM dd_project
WHERE idproj = p_idproj;
lv_projinfo_txt := (rec_ddproj.idproj||', '||rec_ddproj.projname||
', '||rec_ddproj.projstartdate||', '||rec_ddproj.projenddate||
', '||rec_ddproj.projfundgoal||', '||rec_ddproj.projcoord);
P_INFO := LV_PROJINFO_TXT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
P_CHECK :='Please select another project';
DBMS_OUTPUT.PUT_LINE(P_CHECK);
END;
Notes:
A select into may raise NO_DATA_FOUND or TOO_MANY_ROWS.
Good practice is to never handle errors without re-raising the exception, unless your code actually handles the exception. In your case, your code merely sends a signal back to the calling process via the p_check parameter, which moves responsibility for handling the error to the caller. This might be ok in some circumstances but it assumes the caller actually heeds the signal. It would be better to raise an exception which forces the caller to handle it appropriately.
Good practice is to alias all columns and parameters in a query; having a SQL predicate like idproj = p_idproj makes the assumption that the table will never have a column called p_idproj in the future. Instead, it's good practice to deliberately alias all columns and parameters, e.g.
SELECT x.*
into rec_ddproj
FROM dd_project x
WHERE x.idproj = ddproj_sp.p_idproj;

unable to call exception block in oracle

I have trying to execute below pl sql block in my oracle developer edition.I have made calls to functions and procedures and it works fines.But i am not able to call exception in case my query does not get executed.I have been trying to get a wrong query exectued by passing a string value instead of int value.So it throws error but also i need to get exception block to executed in case of such error.Block 2 should through exception as i am passing string value.But exception block does not get call,Any help?? Below is my block
DECLARE
DBCID INT := 102;
CNT INT;
BEGIN
SELECT DEVOPS_ISDBCEXECUTED(DBCID, 'DDL') INTO CNT FROM DUAL;
IF (CNT = 0) THEN
BEGIN
DEVOPS_DBCINSERT (DBCID,'DDL','hsolanki','Prj1','Item1','avarne');
BEGIN
DECLARE W_CNT int;
BEGIN
SELECT COUNT(*) INTO W_CNT FROM HS WHERE NAM = 'DK'; //block 1
IF (W_CNT = 0) THEN
INSERT INTO HS
(NAM, AGE)
VALUES ('Dk',8);
END IF;
END;
END;
BEGIN
DECLARE W_CNT int;
BEGIN
SELECT COUNT(*) INTO W_CNT FROM HS WHERE NAM = 'Ab';
IF (W_CNT = 0) THEN
INSERT INTO HS
(NAM, AGE) //block 2
VALUES ('Ab',s);
END IF;
END;
END;
DEVOPS_DBCUPDATE(DBCID, NULL,'SUCCESS');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('ERROR OCCURED : ' || sqlerrm);
DEVOPS_DBCUPDATE (DBCID,sqlerrm,'Failed');
rollback;
END;
END IF;
END;
Your exception handling block is within the 'IF (CNT = 0) THEN .. END IF' block. If you pass a string value, most probably the exception was thrown at the first function call ( SELECT DEVOPS_ISDBCEXECUTED...), which is not 'protected' by an excpetion handler. You would need to move the exception handler to the outermost block, e.g.:
DECLARE
DBCID INT := 102;
CNT INT;
BEGIN
....
EXCEPTION
WHEN OTHERS THEN
...
END;
So we know ...
DEVOPS_DBCUPDATE is a procedure which updates a table
... and ...
IN exception i am calling DEVOPS_DBCUPDATE ... my table is not getting updated
... and ...
i dont know what is pragma autonomous_transaction
Putting these clues altogether we can see that the rollback in the EXCEPTION block will wipe out the change to the table executed by the preceding call to DEVOPS_DBCUPDATE(), so it only seems as though the EXCEPTION block is not being executed ( a check on whether the DBMS_OUTPUT message is displayed would confirm that it is being called).
Anyway the solution is to make DEVOPS_DBCUPDATE() run in a nested transaction, so the change is applied regardless of what happens in the wider transaction. We do this with the autonomous_transaction pragma.
Obviously I don't know the exact structure of your code, but it will look something like this:
create or replace procedure DEVOPS_DBCUPDATE( ... ) is
pragma autonomous_transaction;
begin
update your_table
set ....
commit;
end;
The COMMIT in the procedure will persist the change to your table but will not save anything in the outer transaction. So the rollback in the EXCEPTION block would still reverse the inserts into the HS table.
Autonomous transactions are very useful when employed properly, but it is easy to misuse them. This scenario - persistent logging in the event of exception or rollback - is their main use case. But generally, use with caution: it's easy to abuse autonomous transactions and end up with a corrupted database. There's more information in the Oracle documentation.
An error raise in the DECLARE section is not handled by that block's EXCEPTION section. For this reason it is often safer to initialise variables after the BEGIN i.e.
DECLARE
DBCID INT;
CNT INT;
BEGIN
DBCID := 'xxx';
...
EXCEPTION
WHEN OTHERS THEN
... -- The assignment will be caught here
END;

How can I modify my PL/SQL procedure to go to my exception handling?

I am using SQL developer to write a procedure.
The objective is to get the name of the drainage system from one table and get the count of how much the drainage system name code appears in another table.
My procedure works, but when I enter an incorrect value, it does not go to the exception section. For example, when I input ‘Mexico River’, this name does not exist in the table. So, I want my exception section to realize that this is an incorrect value being entered.
My question is how do I modify my code, so it can detect incorrect values and go to my exception section.
Below is a snippet of my code:
PROCEDURE number_of_rivers --second procedure with 1 parameter
(input_sysName IN TBLDrainage.DRAINAGE_SYS%TYPE)
is
-- Create a cursor
cursor c_river is
select code, drainage_sys
from TBLDRAINAGE
where DRAINAGE_SYS = input_sysName;
v_rivercount Number;
r_variable c_river %rowtype;
Begin
FOR r_variable in c_river
loop
select count (Drainage_sys) into v_rivercount
from TBLRIVER
where DRAINAGE_SYS = r_variable.code;
DBMS_OUTPUT.PUT_LINE (UPPER(input_sysName) || ' has ' || v_rivercount || ' river(s) in the drainage system.');
end loop;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Error: Please enter a valid drainage system name');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error in finding system');
END ;
The CURSOR..FOR loop has the property of executing zero or more times. It doesn't throw NO_DATA_FOUND.
There are a couple of solutions. One is to include a count inside the loop, and raise an exception afterwards.
l_count := 0;
FOR r_variable in c_river
loop
....
l_count := l_count + 1;
end loop;
if l_count = 0 then
raise NO_DATA_FOUND;
end if;
The other would be to validate the input parameter at the start of your program.
begin
open c_river;
fetch c_river into r_variable;
if c_river%notfound then
raise NO_DATA_FOUND;
else
select count (Drainage_sys)
into v_rivercount
from TBLRIVER
where DRAINAGE_SYS = r_variable.code;
DBMS_OUTPUT.PUT_LINE (UPPER(input_sysName) || ' has ' || v_rivercount || ' river(s) in the drainage system.');
end if;
close c_river;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Error: Please enter a valid drainage system name');
close c_river;
END ;
In this solution I have removed the loop, because I would expect a look-up on drainage system should be unique and return one record. Please re-instate the loop if your data model isn't like that.
I have retained your names for the cursor and its row variables but you should re-name them. They are used for selecting drainage systems not rivers, and their names ought to reflect that. Discipline in naming things is a useful habit to acquire, as misleading variable names will cause confusion in larger chunks of code.
Also, swallowing exceptions like this is very bad:
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error in finding system');
Oracle has thousands of error messages: it better to do nothing with the error message than to throw it away.

Customize PL/SQL exceptions in Oracle

Frequently I found myself doing some functions to insert/delete/update in one or more tables and I've seen some expected exceptions been taken care of, like no_data_found, dupl_val_on_index, etc. For an insert like this:
create or replace FUNCTION "INSERT_PRODUCTS" (
a_supplier_id IN FORNECEDOR.ID_FORNECEDOR%TYPE,
a_prodArray IN OUT PRODTABLE
)
RETURN NUMBER IS
v_error_code NUMBER;
v_error_message VARCHAR2(255);
v_result NUMBER:= 0;
v_prod_id PRODUTO.ID_PROD%TYPE;
v_supplier FORNECEDOR%ROWTYPE;
v_prodInserted PROD_OBJ;
newList prodtable := prodtable();
BEGIN
SELECT FORNEC_OBJ(ID_FORNECEDOR,NOME_FORNECEDOR,MORADA,ARMAZEM,EMAIL,TLF,TLM,FAX) into v_supplier from fornecedor where id_fornecedor = a_supplier_id;
FOR i IN a_prodArray.FIRST .. a_prodArray.LAST LOOP
INSERT INTO PRODUTO (PRODUTO.ID_PROD,PRODUTO.NOME_PROD,PRODUTO.PREC_COMPRA_PROD,PRODUTO.IVA_PROD,PRODUTO.PREC_VENDA_PROD,PRODUTO.QTD_STOCK_PROD,PRODUTO.QTD_STOCK_MIN_PROD)
VALUES (S_PRODUTO.nextval,a_prodArray(i).NOME_PROD,a_prodArray(i).PREC_COMPRA_PROD,a_prodArray(i).IVA_PROD,NULL,NULL,NULL);
/* If the above insert didn't failed, we can insert in weak entity PROD_FORNECIDO. */
SELECT ID_PROD into v_prod_id from PRODUTO where NOME_PROD = a_prodArray(i).NOME_PROD;
INSERT INTO PROD_FORNECIDO VALUES (a_supplier_id, v_prod_id,a_prodArray(i).PREC_COMPRA_PROD);
SELECT PROD_OBJ(ID_PROD,NOME_PROD,PREC_COMPRA_PROD,PREC_VENDA_PROD,QTD_STOCK_PROD,QTD_STOCK_MIN_PROD,IVA_PROD) into v_prodInserted from PRODUTO where ID_PROD= v_prod_id;
a_prodarray(i).ID_PROD := v_prod_id;
END LOOP;
INSERT INTO FORNECPRODS VALUES (a_supplier_id,v_supplier, a_prodarray);
v_result:= 1;
RETURN v_result;
COMMIT;
Exception
When no_data_found then
v_error_code := 0;
v_error_message:= 'Insert Products: One of selects returned nothing';
Insert Into errors Values (v_error_code,v_error_message, systimestamp);
RETURN v_result;
When others Then
ROLLBACK;
v_error_code := SQLCODE;
v_error_message:=substr(SQLERRM,1,50);
Insert Into errors Values (v_error_code,'Error inserting products list',systimestamp);
RETURN v_result;
END;
I would like to customize more of my exceptions or do an exception block for each select/insert. Is that possible or correct?
If so, could please show me some code with important exceptions being throwed by this function?
If you just want to substitute your own error message, there is RAISE_APPLICATION_ERROR...
When no_data_found then
RAISE_APPLICATION_ERROR(-20000
, 'Insert Products: One of selects returned nothing';
, true);
The third parameter returns the original error as well as your custom one.
Oracle also gives us the option to define our exceptions. This can be useful if we want to pass the exception to a calling program...
Declare
no_product_found exception;
Begin
....
When no_data_found then
raise no_product_found;
This would be most effective if we defined the NO_PRODUCT_FOUND exception in a package specification where it could be referenced by external program units.
In addition, Oracle provides the INIT_EXCEPTION pragma which allows us to associate Oracle error numbers with our custom exceptions. Unfortunately we cannot overload error numbers which Oracle has already defined (for instance, we cannot create our own exceptions for ORA-1403 which is already covered by the NO_DATA_FOUND exception). Find out more.
In the exception section; you can raise application error or return 0 with error code explanation. It is about your choice.
If you want to log your errors in the exception section (or in main section), write your own logging procedure with AUTONOMOUS TRANSACTION. so, your logging mechanism is not affected by your main transaction's COMMIT or ROLLBACK. (see: http://www.dba-oracle.com/t_autonomous_transaction.htm)
Another logging mechanism (DML Error Log) in Oracle 10gR2 (and above) is LOG ERRORS clause (see: http://www.oracle-base.com/articles/10g/DmlErrorLogging_10gR2.php).

Resources