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).
Related
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.
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;
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;
I need to handle the ORA-01400 error (cannot insert NULL into ("SCHEMA"."TABLE_NAME"."COLUMN_NAME") ) using a exception handle.
ORACLE Predefine a few Exceptions like (ACCESS_INTO_NULL, ZERO_DIVIDE and so on), but apparently does not define an Exception for the ORA-01400 error, how do I handle this particular error code?
I need something like this (other suggestions are accepted).
....
...
INSERT INTO MY_TABLE (CODE, NAME) VALUES (aCode,aName);
COMMIT;
EXCEPTION
WHEN NULL_VALUES THEN /* i don't know this value , exist?*/
Do_MyStuff();
WHEN OTHERS THEN
raise_application_error(SQLCODE,MY_OWN_FORMAT_EXCEPTION(SQLCODE,SQLERRM),TRUE);
END;
The pre-defined PL/SQL exceptions are special to Oracle. You really can't mess with those. When you want to have a set of predefined exceptions of your own you can't declare them "globally" like the standard ones. Instead, create an exceptions package which has all of the exception declarations and use that in your application code.
Example:
CREATE OR REPLACE PACKAGE my_exceptions
AS
insert_null_into_notnull EXCEPTION;
PRAGMA EXCEPTION_INIT(insert_null_into_notnull, -1400);
update_null_to_notnull EXCEPTION;
PRAGMA EXCEPTION_INIT(update_null_to_notnull, -1407);
END my_exceptions;
/
Now use the exception defined in the package
CREATE OR REPLACE PROCEDURE use_an_exception AS
BEGIN
-- application specific code ...
NULL;
EXCEPTION
WHEN my_exceptions.insert_null_into_notnull THEN
-- application specific handling for ORA-01400: cannot insert NULL into (%s)
RAISE;
END;
/
Source: http://www.orafaq.com/wiki/Exception
you can define your own exceptions, like variables (they will have the same scope as other variables so you can define package exception, etc...):
SQL> DECLARE
2 NULL_VALUES EXCEPTION;
3 PRAGMA EXCEPTION_INIT(NULL_VALUES, -1400);
4 BEGIN
5 INSERT INTO t VALUES (NULL);
6 EXCEPTION
7 WHEN null_values THEN
8 dbms_output.put_line('null value not authorized');
9 END;
10 /
null value not authorized
PL/SQL procedure successfully completed
You can handle exception by its code like this:
....
...
INSERT INTO MY_TABLE (CODE, NAME) VALUES (aCode,aName);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -1400 THEN
Do_MyStuff();
ELSE
raise_application_error(SQLCODE,MY_OWN_FORMAT_EXCEPTION(SQLCODE,SQLERRM),TRUE);
END IF;
END;
INSERT INTO MY_TABLE (CODE, NAME) VALUES (aCode,aName);
COMMIT;
EXCEPTION
WHEN NULL_VALUES /* i don't know this value , exist?*/
emesg := SQLERRM;
dbms_output.put_line(emesg);
WHEN OTHERS THEN
emesg := SQLERRM;
dbms_output.put_line(emesg);
END;
SQLERRM shows the sql error message
http://www.psoug.org/reference/exception_handling.html
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.