Real error text with BULK_EXCEPTIONS in Oracle - oracle

Is it possible to know real reason, real ora code (in this case unique key violation) when using
FORALL var_i_idx IN 1..table_of_t.count SAVE EXCEPTIONS
INSERT INTO TABLE_A VALUES (
table_of_t(var_i_idx).var_id);
RETURN retVal;
EXCEPTION
WHEN OTHERS
THEN
IF SQLCODE = -24381
THEN
FOR var_i_idx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
v_action := 'Insert table_of_t';
v_error_text := 'Unexcpected exception SQLCODE: ' || SQL%BULK_EXCEPTIONS (var_i_idx).ERROR_INDEX || ' -- ERROR:' || sqlerrm(SQL%BULK_EXCEPTIONS (var_i_idx).ERROR_CODE);
dbms_output.put_line('Unexcpected exception SQLCODE: '||SQLCODE||' -- ERROR: '||SQLERRM);
DBMS will print following :Unexcpected exception SQLCODE: -24381 -- ERROR: ORA-24381: error(s) in array DML
But is it possible to know real reason ?

Yes, you can get the actual error message, although only the base message. The issue you have is your dbms_output message is for the bulk error not the individual item errors. You get the item error from the ERROR_CODE in the SQL%BULK_EXCEPTIONS collections. Even more you access the actual data causing the exception through ERROR_INDEX. This indexes the entry in the collection named in the forall statement. Skeleton access for error message and bulk collection (where blk_array is the name of the collection used in the forall). See fiddle;
for err_idx in 1 .. sql%bulk_exceptions
loop
msg := sqlerrm(-sql%bulk_exceptions(err_idx).error_code);
val := blk_array(sql%bulk_exceptions(err_idx).error_index);
end loop;
In the fiddle ignore the code setting up the FORALL, that was available for another task entirely. Just concentrate on the Exception section except for the lines:
declare
errors_during_forall exception; -- create name for errors during bulk, forall, processing
pragma exception_init (errors_during_forall, -24381); -- and tie that name to the actual Oracle error number
This allows you to define a name to the exception and then reference that name is the EXCEPTION section.

Related

Which command raised the exception?

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!!

PL/SQL check SQL excution if it went OK or not?

I have a few SQL (Select/Update/Insert) syntax that I will run inside PL/SQL one after another
is there any way to check if each syntax completed correctly and if there is some error it will not halt the whole PL/SQL, it will just return "OK" or "Not OK" to a variable so I can use it with IF?
UPDATE
I came up with this function, but it dose not seems to work, it returns 0 all time!
create or replace
FUNCTION EXECUTE_SQL(
V_SQL IN VARCHAR2 )
RETURN NUMBER
AS
V_RESULTS NUMBER := 1;
BEGIN
BEGIN
EXECUTE IMMEDIATE V_SQL;
EXCEPTION
WHEN OTHERS THEN
-- the following line is just for debugging!
dbms_output.put_line(SQLERRM);
V_RESULTS:= 0;
END;
RETURN V_RESULTS;
END EXECUTE_SQL;
what is wrong wit it (if any)!
cheers
if sql%rowcount > 0 then
-- insert or update statement affected sql%rowcount rows
end if;
As for the correct syntax: if the syntax is wrong, it won't even compile. If there's a data consistency error (such as divide by 0 error, or primary key violation) an exception will be thrown. Such exception can be caught in exception handlers
In the exception handler, you can then check sqlerrm for more details:
begin
update t set x = ...
exception when others then
dbms_output.put_line(SQLERRM);
end;
There are also a few predefined exceptions that you can check on:
begin
update t set x = ...
exception
when DUP_VAL_ON_INDEX
-- primary key or unique key violation
when OTHERS
-- other kind of exception
end;
If the syntax is not correct the entire block will be invalid, so you'll not be able to run it.
If you want to run all statements, despite that one can raise an exception, you can:
BEGIN
BEGIN
statement1;
EXCEPTION
when exception1 then
some commands or null;
when exception2 then
some commands or null;
END;
BEGIN
statement2;
EXCEPTION
when exception3 then
some commands or null;
when exception4 then
some commands or null;
END;
etc.
END;
Write show errors
begin
update t set x = ...
exception
when DUP_VAL_ON_INDEX
-- primary key or unique key violation
when OTHERS
-- other kind of exception
end;
/
show errors
It will show errors if any.

Checking for Error during execution of a procedure in oracle

create or replace procedure proc_advertisement(CustomerID in Number,
NewspaperID in number,
StaffID in Number,
OrderDate in date,
PublishDate in date,
Type in varchar,
Status in varchar,
Units in number) is
begin
insert into PMS.Advertisement(CustomerID, NewspaperID, StaffID, OrderDate, PublishDate,
Type, Status, Units)
values(CustomerID,NewspaperID, StaffID, OrderDate, PublishDate,
Type, Status, Units);
dbms_output.put_line('Advertisement Order Placed Successfully');
end;
How to check for if any error has occurred during the execution of the procedure and if any error has occurred then I wish to display an error message.
First of all, Oracle itself will raise an error message if any error occurs while running the procedure - for example:
ORA-02291: integrity constraint (EMP.MGR_FK) violated - parent key not Found
You can handle errors explicitly by writing an exception handler, but unless you do this well you are quite likely to just obfuscate the problem. For example you could simply add this (just before the END of your procedure:
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error occured');
But now your user won't know what kind of error, whereas before they could infer that it was that the specified Manager did not exist. You could show the original error also like this:
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error occured: '||SQLERRM);
if that adds any value. Or you could just show a generic error and then write the value of SQLERRM to a log table.
You can also handle particular exceptions: for example
PROCEDURE ... IS
e_invalid_fk EXCEPTION;
PRAGMA EXCEPTION_INIT(e_invalid_fk,-2291);
BEGIN
...
EXCEPTION
WHEN e_invalid_fk THEN
IF SQLERRM LIKE '%(EMP.MGR_FK)%' THEN
raise_application_error(-20001,'Invalid manager specified');
ELSE
RAISE;
END IF;
END;
Note the RAISE: if any part of your exception handler doesn't issue either a RAISE or a RAISE_APPLICATION_ERROR then you are effectively sweeping the exception under the carpet - the user will think the procedure worked.
By the way, DBMS_OUTPUT.PUT_LINE is great for trying things out and debugging, in SQL Plus or an IDE, but it has no place in real code as users and applications that call the procedure will never see the output it produces.

Providing a more meaningful message when an error is raised in PL/SQL

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.

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