PL/SQL exception handling - log errors - oracle

How do I record the oracle error in a pl/sql script? I have been to the oracle error handling documentation and I see the built in exceptions, but what if I do not know what the exception is? How can I log this in an exception block?
I want to do something like the below.
exception
when others then DBMS_OUTPUT.PUT_LINE(the error)

It's easier to create error logging database trigger with conditional logging, for example my actual error_logging trigger: https://github.com/xtender/xt_scripts/blob/master/error_logging/on_database.sql
create table ERROR_LOG
(
id NUMBER,
username VARCHAR2(30),
errcode INTEGER,
seq INTEGER,
tmstmp TIMESTAMP(6),
msg VARCHAR2(4000),
sql_text CLOB
)
/
create sequence err_seq
/
create or replace trigger trg_error_logging
after servererror
on database
disable
declare
v_id number := err_seq.nextval();
v_tmstmp timestamp:= systimestamp;
n int;
sql_text dbms_standard.ora_name_list_t;
v_sql_text clob;
begin
-- only if plsql_debug is set to TRUE:
for r in (select * from v$parameter p where p.name='plsql_debug' and upper(p.value)='TRUE') loop
v_sql_text:=null;
n := ora_sql_txt(sql_text);
for i in 1..n loop
v_sql_text := v_sql_text || sql_text(i);
end loop;
for i in 1.. ora_server_error_depth
loop
if i=1 then
insert into error_log(id,seq,tmstmp,username,errcode,msg,sql_text)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i), v_sql_text);
else
insert into error_log(id,seq,tmstmp,username,errcode,msg)
values( v_id, i, v_tmstmp, user, ora_server_error(i), ora_server_error_msg(i) );
end if;
end loop;
commit;
end loop;
END;
/
select object_name,object_type,status from user_objects o where object_name='TRG_ERROR_LOGGING'
/
alter trigger trg_error_logging enable
/
As you can see it logs all errors into the table ERROR_LOG, but only if session parameter plsql_debug is set to true. Obviously, you can change it to own parameters or conditions.

Related

Ignore lines that causes errors

I have a big Oracle script with thousands of package call inside a BEGIN - END;
Is there a way to ignore the lines that causes error and continue executing the next lines? Some sort of "On Error Resume Next" in vb.
If you have only one BEGIN END section, then you can use EXCEPTION WHEN OTHERS THEN NULL.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
exception when others then null;
end;
SQL> /
PL/SQL procedure successfully completed.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
--exception when others then null;
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 6
SQL>
The whole concept of "ignore errors" is a bug, and a lie if any errors occur. That is not to say you cannot trap errors and continue processing, just that you MUST handle the errors. For example, assume the use case: "Data has been loaded into a stage table from multiple .csv files. Now load into the tables A and Table B according to ....".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, col_a2);
insert into table_b (col1, col2)
values (rec.col_b1, col_b2);
exception
when others then null;
end;
end loop;
process_message := 'Load Tables A,B Complete';
end ;
Now suppose a user created the a .csv file entered "n/a" in numeric columns where there was no value or the value was unknown. The result of this all too common occurrence is all such rows were not loaded, but you have no way to know that until the user complains their data was not loaded even though you told them it was. Further you have no way of determining the problem.
A much better approach is to "capture and report".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
load_error_occurred boolean := False;
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, rec.col_a2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
begin
insert into table_b (col1, col2)
values (rec.col_b1, rec.col_b2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
end loop;
if load_error_occurred then
process_message := 'Load Tables A,B Complete: Error(s) Detected';
else
process_message := 'Load Tables A,B Complete: Successful No Error(s)';
end if;
end Load_Tables_A_B_from_Stage ;
Now you have informed the user of the actual status, and where you are contacted you can readily identify the issue.
User here is used in the most general sense. It could mean a calling routine instead of an individual. Point is you do not have to terminate your process due to errors but DO NOT ignore them.
I don't think there is any magic one-liner that will solve this.
As others have, use a editor to automate the wrapping of each call within a BEGIN-EXCEPTION-END block might be quicker/easier.
But, if feel a little adventurous, or try this strategy:
Let's assume you have this:
BEGIN
proc1;
proc2;
proc3;
.
.
.
proc1000;
END;
You could try this (untested, uncompiled but might give you an idea of what to try):
DECLARE
l_progress NUMBER := 0;
l_proc_no NUMBER := 0;
e_proc_err EXCEPTION;
-- A 'runner' procedure than manegrs the counters and runs/skips dpending on these vals
PROCEDURE run_proc ( pname IN VARCHAR2 ) IS
BEGIN
l_proc_no := l_proc_no + 1;
IF l_proc_no >= l_progress
THEN
-- log 'Running pname'
EXECUTE IMMEDIATE 'BEGIN ' || pname || '; END;' ;
l_progress := l_progress + 1;
ELSE
-- log 'Skipping pname'
END IF;
EXCEPTION
WHEN OTHERS THEN
-- log 'Error in pname'
l_progress := l_progress + 1;
RAISE e_proc_err;
END;
BEGIN
l_progress := 0;
<<start>>
l_proc_no := 0;
run_proc ( 'proc1' );
run_proc ( 'proc2' );
run_proc ( 'proc3' );
.
.
run_proc ( 'proc1000' );
EXCEPTION
WHEN e_proc_err THEN
GOTO start;
WHEN OTHERS THEN
RAISE;
END;
The idea here is to add a 'runner' procedure to execute each procedure dynamically and log the run, skip, error.
We maintain a global count of the current process number (l_proc_no) and overall count of steps executed (l_progress).
When an error occurs we log it, raise it and let it fall into the outer blocks EXCEPTION handler where it will restart via an (evil) GOTO.
The GOTO is placed such that the overall execution count is unchanged but the process number is reset to 0.
Now when the run_proc is called it sees that l_progress is greater than l_proc_no, and skips it.
Why is this better than simply wrapping a BEGIN EXCEPTION END around each call?
It might not be, but you make a smaller change to each line of code, and you standardise the logging around each call more neatly.
The danger is a potential infinite loop which is why I specify e_proc_err to denote errors within the called procedures. But it might need tweaking to make it robust.

WHEN-BUTTON-PRESSED trigger raise unhendled exception ORA-01407

I am new in PL SQL and I am trying to resolve problem with copy-past data from .CVS file to database
I create a small application which will take data from .CVS and past it to database.
I create a method, but after I compile it's writtend Successfully compiled
But when I run form I get error
WHEN-BUTTON-PRESSED trigger raise unhendled exception ORA-01407
Does anyone know what this means since I google it and could not find anything ?
I would be very thankfull
declare
import_file text_io.file_type;
import_file_name varchar2(1000);
import_log_file text_io.file_type;
import_log_file_name varchar2(1000);
vec_importovano number;
brojac number;
brojac_redova number;
linebuf varchar2(5000);
p_rbr varchar2(4);
p_polica varchar2(20);
p_banka varchar2 (20);
p_kontakt varchar2(20);
kraj_fajla number;
begin
import_file_name := :Global.Lokacija_prenosa||:import.naziv_fajla||:Global.Ekstenzija_prenosa;
import_file := text_io.fopen(import_file_name,'r');
--p_rbr := 100000;
delete from zivot_trajni_nalog_ponude where banka is not null;
commit;
kraj_fajla := 0;
while kraj_fajla = 0 loop
begin
text_io.get_line(import_file, linebuf);
if brojac_redova>=2 then
if length(linebuf)>100 then
p_rbr:=substr(linebuf, 1, instr(linebuf,';',1,1)-1);
p_polica:=substr(linebuf, instr(linebuf,';',1,1)+1, instr(linebuf,';',1,2) - instr(linebuf,';',1,1)-1);
p_banka:=substr(linebuf, instr(linebuf,';',1,2)+1, instr(linebuf,';',1,3) - instr(linebuf,';',1,2)-1);
p_kontakt:=substr(linebuf, instr(linebuf,';',1,3)+1, instr(linebuf,';',1,4) - instr(linebuf,';',1,3)-1);
select count(*)
into vec_importovano
from ZIVOT_TRAJNI_NALOG_PONUDE
where broj_police=p_polica and p_rbr=redni_broj;
if vec_importovano=0 then
insert into ZIVOT_TRAJNI_NALOG_PONUDE values(p_rbr, p_polica, p_banka, p_kontakt);
commit;
end if;
end if;
end if;
EXCEPTION WHEN NO_DATA_FOUND THEN kraj_fajla := 1;
end;
end loop;
update zivot_trajni_nalog_ponude set redni_broj = p_rbr;
commit;
text_io.fclose(import_file);
message('Zavrseno prepisivanje fajla');
end;
The error you got (ORA-01407) means that you are trying to update a column (which is set to NOT NULL) with a NULL value. That won't work. For example:
SQL> create table test (id number not null);
Table created.
SQL> insert into test (id) values (100);
1 row created.
SQL> update test set id = null;
update test set id = null
*
ERROR at line 1:
ORA-01407: cannot update ("SCOTT"."TEST"."ID") to NULL
SQL>
The only UPDATE in your code is this:
UPDATE zivot_trajni_nalog_ponude SET redni_broj = p_rbr;
Apparently, p_rbr is NULL, redni_broj won't accept it and you got the error.
What to do? Debug your code and see why p_rbr doesn't have a value. A simple "solution" might be
IF p_rbr IS NOT NULL
THEN
UPDATE zivot_trajni_nalog_ponude
SET redni_broj = p_rbr;
END IF;
Also, although not related to your problem: don't COMMIT within a loop.
ORA-01407 occurs as you are trying to update/Insert a column to NULL
when the column does not accept NULL values.
To find all the "not null" columns in table ZIVOT_TRAJNI_NALOG_PONUDE, Please check the DDL of the table.

trying to add an agent (not allowing duplicate last names). Initial total sales should be zero

I am receiving the following error when trying to compile and execute. I am having issues how to figure this out.
14/7 PL/SQL: Statement ignored
14/10 PLS-00204: function or pseudo-column 'EXISTS' may be used inside a SQL statement only
Errors: check compiler log
CREATE OR REPLACE PROCEDURE AddAgent(
p_Agent_Fname IN Agent.Agent_Fname%TYPE,
p_Agent_Lname IN Agent.Agent_Lname%TYPE,
p_Agent_Address IN Agent.Agent_Address%TYPE,
p_Agent_Tsales IN Agent.Agent_Tsales%TYPE,
p_Agent_Salary IN Agent.Agent_Salary%TYPE)
IS
p_ErrorCode number; --USED FOR ERROR CHECKING
p_ErrorMsg Varchar2(200);
p_CurrentUser Varchar2(100);
BEGIN
IF EXISTS
(SELECT * FROM Agent WHERE Agent_Lname = p_Agent_Lname) THEN
dbms_output.put_line('Failure');
ELSE
INSERT INTO Agent (Agent_Fname, Agent_Lname, Agent_Address, Agent_Tsales, Agent_Salary)
SELECT p_Agent_Fname, p_Agent_Lname, p_Agent_Address, 0, p_Agent_Salary
from Dual;
COMMIT;
dbms_output.put_line('Success');
END IF;
END;
As you were told, you can't use EXISTS out of the SELECT statement. Therefore, you'll have to check for existence in the AGENT table elsewhere.
Here's one option you might consider. Note that I've also rewritten the INSERT INTO statement - there's no need to SELECT FROM DUAL, you already have all those values.
CREATE OR REPLACE PROCEDURE AddAgent(
p_Agent_Fname IN Agent.Agent_Fname%TYPE,
p_Agent_Lname IN Agent.Agent_Lname%TYPE,
p_Agent_Address IN Agent.Agent_Address%TYPE,
p_Agent_Tsales IN Agent.Agent_Tsales%TYPE,
p_Agent_Salary IN Agent.Agent_Salary%TYPE)
IS
p_ErrorCode number; --USED FOR ERROR CHECKING
p_ErrorMsg Varchar2(200);
p_CurrentUser Varchar2(100);
l_cnt number; --> newly added
BEGIN
-- check whether something exists in a table
select count(*)
into l_cnt
from dual
where exists (select null
from agent
where agent_lname = p_agent_lname
);
IF l_cnt > 0 then
dbms_output.put_line('Failure');
ELSE
INSERT INTO Agent
(Agent_Fname, Agent_Lname, Agent_Address, Agent_Tsales, Agent_Salary)
VALUES
(p_Agent_Fname, p_Agent_Lname, p_Agent_Address, 0, p_Agent_Salary);
COMMIT;
dbms_output.put_line('Success');
END IF;
END;
/

ORACLE : How to take a count rows in a table inside a procedure while the table is being populated by another procedure (oracle job)

I want to take count of a table inside a parent procedure while the table is being populated by another/child procedure. This second/chile procedure is started as a job from inside the first/parent.
The above is a simplistic description of certain decisions I need to take inside the first/parent based on the count. Example, If the row-count value of the table read inside the first/parent reaches a certain value I want to kill/remove the second/child job.
However, I am always getting the count(*) = 0 inside the first/parent, even though a select * on the table shows that the child procedure is populating the table.
What could be the reason and what is the way of getting a count ?
Below is a test script :
create table test_j(col1 number);
create or replace PROCEDURE parent_job_sp
as
g_jobid_child number := -1;
v_child_cnt number := -1;
begin
DBMS_OUTPUT.PUT_LINE ('START - parent_job_sp');
DBMS_JOB.SUBMIT(g_jobid_child,'GFF_LOAD.child_job_sp;');
COMMIT;
DBMS_OUTPUT.PUT_LINE ('g_jobid_child='||g_jobid_child);
while (v_child_cnt<=8)
loop
DBMS_OUTPUT.PUT_LINE ('v_child_cnt='||v_child_cnt);
execute immediate 'select count(*) from test_j' into v_child_cnt;
if v_child_cnt >= 8
then
BEGIN
DBMS_JOB.remove(g_jobid_child);
COMMIT;
DBMS_OUTPUT.PUT_LINE ('REMOVED JOB '||g_jobid_child);
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE=-23421
THEN DBMS_OUTPUT.PUT_LINE('THEN');
ELSE DBMS_OUTPUT.PUT_LINE('ELSE');
END IF;
END;
end if;
DBMS_OUTPUT.PUT_LINE ('OUTSIDE IF');
end loop;
DBMS_OUTPUT.PUT_LINE ('OUTSIDE WHILE');
exception --- proc-level exception
when others then
DBMS_OUTPUT.PUT_LINE('parent_job_sp - '||SQLERRM);
end; --- end proc
create or replace PROCEDURE child_job_sp
as
PRAGMA AUTONOMOUS_TRANSACTION;
begin
for cur_test in
(
select rownum row_num from dual connect by level<=1000
)
loop
insert into test_j values (cur_test.row_num);
commit;
end loop;
COMMIT;
exception --- proc-level exception
when others then
DBMS_OUTPUT.PUT_LINE('child_job_sp - '||SQLERRM);
end; --- end proc
--- then run from SQL*Plus : exec parent_job_sp
--- I always get v_child_cnt=0 even though the result of the select count(*) from test_j is 1000.
What could be the reason and what is the way of getting a count ?

How to develop an after serverror trigger in Oracle?

I'm trying to log all the errors in my database into a table. So as user sys i wrote the following code:
CREATE TABLE servererror_log (
error_datetime TIMESTAMP,
error_user VARCHAR2(30),
db_name VARCHAR2(9),
error_stack VARCHAR2(2000),
captured_sql VARCHAR2(1000));
/
CREATE OR REPLACE TRIGGER log_server_errors
AFTER SERVERERROR
ON DATABASE
DECLARE
captured_sql VARCHAR2(1000);
BEGIN
SELECT q.sql_text
INTO captured_sql
FROM gv$sql q, gv$sql_cursor c, gv$session s
WHERE s.audsid = audsid
AND s.prev_sql_addr = q.address
AND q.address = c.parent_handle;
INSERT INTO servererror_log
(error_datetime, error_user, db_name,
error_stack, captured_sql)
VALUES
(systimestamp, sys.login_user, sys.database_name,
dbms_utility.format_error_stack, captured_sql);
END log_server_errors;
But when i force an error like trying to select from a non-existing table it doesn´t log the error in the table.
Is there any way to check that the trigger fires at all? Also, I tried creating a test table to insert there but it doesn't work either, even if a define the trigger as an autonomous transaction and commit inside the trigger.
Thanks,
Joaquin
Do not query v$sql; get the statement using ora_sql_txt.
CREATE OR REPLACE TRIGGER log_server_errors
AFTER SERVERERROR
ON DATABASE
DECLARE
sql_text ora_name_list_t;
stmt clob;
n number;
BEGIN
n := ora_sql_txt(sql_text);
if n > 1000 then n:= 1000; end if ;
FOR i IN 1..n LOOP
stmt := stmt || sql_text(i);
END LOOP;
INSERT INTO servererror_log
(error_datetime, error_user, db_name,
error_stack, captured_sql)
VALUES
(systimestamp, sys.login_user, sys.database_name,
dbms_utility.format_error_stack, stmt);
commit;
END log_server_errors;
/
Then:
SQL> select * from c;
This produces:
select * from c
*
ERROR at line 1:
ORA-00942: table or view does not exist
That can now be queried:
select * from servererror_log;
To produce:
ERROR_DATETIME
---------------------------------------------------------------------------
ERROR_USER DB_NAME
------------------------------ ---------
ERROR_STACK
--------------------------------------------------------------------------------
CAPTURED_SQL
--------------------------------------------------------------------------------
11-FEB-09 02.55.35.591259 PM
SYS TS.WORLD
ORA-00942: table or view does not exist
select * from c
To see if the trigger is firing, add one or more lines to it like this:
DBMS_OUTPUT.PUT_LINE( 'Got this far' );
In SQLPlus, SET SERVEROUTPUT ON then execute a command to generate an error. You should get output like this:
dev> select * from aldfjh;
select * from aldfjh
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-00942: table or view does not exist
Got this far
Check the status of your trigger and/or the existence of other triggers with:
select trigger_name, status
from all_triggers
where triggering_event like 'ERROR%'
This should result into:
TRIGGER_NAME STATUS
------------ -------
LOG_SERVER_ERRORS ENABLED
If trigger is not enabled or another trigger fails, it probably will not work.
Save this as ORA-00942.sql:
-- Drop trigger and ignore errors (e.g., not exists).
DECLARE
existential_crisis EXCEPTION;
PRAGMA EXCEPTION_INIT( existential_crisis, -4080 );
BEGIN
EXECUTE IMMEDIATE 'DROP TRIGGER TRG_CATCH_ERRORS /*+ IF EXISTS */';
EXCEPTION WHEN existential_crisis THEN
DBMS_OUTPUT.PUT_LINE('Ignoring non-existence.');
END;
/
-- Drop table and ignore errors (e.g., not exists).
DECLARE
existential_crisis EXCEPTION;
PRAGMA EXCEPTION_INIT( existential_crisis, -942 );
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE TBL_ERROR_LOG /*+ IF EXISTS */';
EXCEPTION WHEN existential_crisis THEN
DBMS_OUTPUT.PUT_LINE('Ignoring non-existence.');
END;
/
-- Create the table (will not exist due to drop statement).
CREATE TABLE TBL_ERROR_LOG (
occurred timestamp,
account varchar2(32),
database_name varchar2(32),
stack clob,
query clob
);
-- Create the trigger to log the errors.
CREATE TRIGGER TRG_CATCH_ERRORS AFTER servererror ON database
DECLARE
sql_text ora_name_list_t;
n number;
query_ clob;
BEGIN
n := ora_sql_txt( sql_text );
IF n > 1000 THEN n := 1000; END IF;
FOR i IN 1 .. n LOOP
query_ := query_ || sql_text( i );
END LOOP;
INSERT INTO TBL_ERROR_LOG
(occurred, account, database_name, stack, query)
VALUES
(systimestamp, sys.login_user, sys.database_name,
dbms_utility.format_error_stack, query_);
END;
/
Run using sqlplus:
SQL> #ORA-00942.sql
PL/SQL procedure successfully completed.
PL/SQL procedure successfully completed.
Table created.
Trigger created.
Test it:
select * from blargh;
select * from TBL_ERROR_LOG;
Output:
2017-10-20 15:15:25.061 SCHEMA XE "ORA-00942: table or view does not exist" select * from blargh

Resources