how to insert the result of dbms_output.put_line to a table for error catch? - oracle

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.

Related

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;

How to handle exceptions in for loop for insert rows using a stored procedure? Oracle PLSQL

I'm coding a complex PLSQL block (complex for me hahaha) to insert rows using information from the FOR LOOP CURSOR and add parameters to insert using a stored procedure. The current problem is there are around 200 rows to be inserted but when a simple row fail to insert all rows inserted broke and oracle execute a ROLLBACK command (I think so). So... How could I handle exceptions to insert succefully all rounds I can and when any rows fail show it in screen? Thanks
FOR i IN c_mig_saldos LOOP
IF i.tipo_comprobante = 'P' THEN -- Nota de debito (positivo)
v_cmp_p.prn_codigo := 'VIV';
v_cmp_p.tcm_codigo := 'NRA';
v_cmp_p.cmp_fecha_emision := TRUNC(SYSDATE);
v_cmp_p.cmp_fecha_contable := TRUNC(SYSDATE);
v_cmp_p.cmp_observacion := 'GENERACION AUTOMATICA DE SALDOS';
v_cmp_p.cli_codigo := i.cli_codigo;
v_tab_dco_p(1).cnc_codigo := 'VIA';
v_tab_dco_p(1).dco_precio_unitario := i.total_final;
v_tab_dco_p(1).dco_cantidad := 1;
v_tab_dco_p(1).dco_importe := i.total_final;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
COMMIT;
END IF;
END LOOP;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
begin
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
exception
when others then --this could be made more specific but you didn't say what type of error you were getting
-- Log to a table so you can export failed inserts later.
-- Make sure log table cols are large enough to store everything that can possibly be inserted here...
ErrorLogProc(the, cols, you, want, to, see, and, SQLERRM);
end;
In the ErrorLogProc() I'd recommend a couple things, here is a snippet of some things I do in my error logging proc that you may find helpful (it's just a few snippets, not intended to fully work, but you should get the idea)...
oname varchar2(100);
pname varchar2(100);
lnumb varchar2(100);
callr varchar2(100);
g1B CHAR(2) := chr(13)||chr(10);
PRAGMA AUTONOMOUS_TRANSACTION; --important!
begin
owa_util.who_called_me(oname, pname, lnumb, callr);
--TRIM AND FORMAT FOR ERRORLOG
lv_errLogText := 'Package: '||pname||' // Version: '||version_in||' // Line Number: '||lnumb||' // Error: ';
lv_string1 := mid(errStr_in,1,4000-Length(lv_errLogText));
lv_errLogText := lv_errLogText||lv_string1;
lv_errLogText := lv_errLogText||g1B||'Error Backtrace: '||replace(dbms_utility.format_error_backtrace,'ORA-', g1b||'ORA-');
insertIntoYourErrorLogTable(lv_errLogText, and, whatever, else, you, need);
commit;
To keep this simple, since there's not enough information to know the what and why of the question, this will kick out some text information about failures as desired.
SQL> set serveroutput on
Then here's an anonymous PL/SQL block:
BEGIN
FOR i IN c_mig_saldos
LOOP
-- not relevant
BEGIN
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
EXCEPTION
-- The goal of this is to ignore but output information about your failures
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('whatever you want about v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p or SQLERRM/SQLCODE or the error stack - not enough info in the question');
END;
END LOOP;
END;
/
Note: I have removed the commit from the execution.
Then if you like what you see...
SQL> commit;
Ideally, if I knew more about why the insert failures were expected to occur and what I wanted to do about them, I would not insert them in the first place.
Agree with comment that more information is needed, but a couple of things to consider:
Does this need to be done as a loop - if you can write your query as a select statement, then you can do a simple insert without the need for PLSQL, which would be simpler and likely quicker (set based SQL vs row-by-row PLSQL)
You say a ROLLBACK is occuring - you have a commit inside your IF statement, so any records which make it into your IF statement and succesfully make it through your insert procedure will be committed i.e. they will not be rolled back; You should consider if some records you think are being rolled back are actually not making it into the IF statement at all
Can you provide example data, or an example of the error you are receiving?

Why does this Oracle PLSQL function need to be recompiled when a table is updated?

I have written this function for an Oracle db for a class project:
create or replace Function loginUser ( name_in IN varchar2, pass_in IN varchar2 )
Return Number
IS
cursor c1 is
SELECT u_id
FROM userinfo
WHERE username = name_in AND pass = pass_in AND lockedout='N';
user_id_return number;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
open c1;
fetch c1 into user_id_return;
if c1%notfound then
user_id_return := 0;
INSERT INTO LoginAttempt(username, whenattempted, attempt_status) VALUES (name_in, SYSDATE, 'N');
commit;
ELSE
INSERT INTO LoginAttempt(username, whenattempted, attempt_status) VALUES (name_in, SYSDATE, 'Y');
commit;
END IF;
close c1;
Return user_id_return;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END;
It works great, I get an insert into a table called LoginAttempt when you call
SELECT loginUser('name','pass') FROM DUAL;
The issue, however, is that new records and updates to userinfo are not reflected in the SELECT statement at the top of the function.
I have to recompile the function each time I update the userinfo table.
Why is this? Is this how functions with SELECT statements work? Is the table that is being SELECTED from compiled when the function is compiled?
Is this related to the PRAGME AUTONOMOUS_TRANSACTION bit?
The schema for the table can be found on github (https://github.com/tmsimont/cs3810schema/blob/master/export.sql)
Because your function executes its DML in an autonomous transaction (i.e. a separate session to the calling one), it cannot see data that has not been committed by the calling session. Until your code commits, the changes it makes are not visible to any other session, including the one used by your function.
This has nothing to do with needing to compile the function. The reason compilation caused it to suddenly see the data is because DDL always issues a commit, thus having the side effect of committing the data you had inserted.

Trigger to RAISE_APPLICATION_ERROR but execute inner commands

I'm working on the following PL/SQL trigger:
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
FOR EACH ROW
DECLARE
BEGIN
IF :OLD.type = 'PRESIDENT' THEN
INSERT INTO trigger_log VALUES (sysdate, 'Nope.', 'No change.');
RAISE_APPLICATION_ERROR(-20111, 'Can not change!');
END IF;
END;
Here, I want to cancel the UPDATE command on the worker table, when a PRESIDENT's payment is about to get changed. At the same time, I wish to log this command into table called trigger_log. The problem is, when I RAISE_APPLICATION_ERROR the UPDATE got cancelled, but the logging (INSERT INTO trigger_log) aswell. How can I RAISE_APPLICATION_ERROR or throw an EXCEPTION, but still have all commands to be run inside the TRIGGER?
You have to commit your INSERT-Statement before raising the error.
Maybe think about AUTONOMOUS_TRANSACTION
EDIT
As others have stated already, you should not and cannot commit inside a trigger (exception autonomous transaction). So think about using the solution of Tom Thomas or calling a logging-procedure/-package.
You can call a stored procedure from your trigger. That stored procedure should be declared as PRAGMA AUTONOMOUS_TRANSACTION. Try like this,
CREATE OR REPLACE
PROCEDURE log_error_p
AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO trigger_log VALUES (SYSDATE, 'Nope.', 'No change.');
COMMIT;
END;
--
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
FOR EACH ROW
DECLARE
BEGIN
IF :OLD.TYPE = 'PRESIDENT' THEN
log_error_p();
RAISE_APPLICATION_ERROR(-20111, 'Can not change!');
END IF;
END;
/
First of all, never use a commit or a rollback inside the trigger. It's a coding standard. As for your question i think this is a better way than raise_application_error
CREATE OR REPLACE TRIGGER trigger_1
BEFORE UPDATE ON worker
REFERENCING OLD as o AND NEW as n
FOR EACH ROW
DECLARE
InsertException EXCEPTION;
BEGIN
IF o.TYPE = 'PRESIDENT' THEN
RAISE InsertException;
END IF;
EXCEPTION
WHEN InsertException THEN
INSERT INTO trigger_log VALUES (SYSDATE, 'Nope.', 'No change.');
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

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