How to develop an after serverror trigger in Oracle? - 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

Related

PL/SQL exception handling - log errors

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.

Checking the date before inserting in a table ORACLE 10g

I have table:
BOOK_DT1 BOOK_DT2 USERNAME
--------- --------- --------------------
22-SEP-17 12-OCT-17 rSK
08-FEB-16 18-FEB-16 chak
05-JAN-17 12-JAN-17 rah
31-JAN-16 01-JUL-16 ABC
While inserting another column, it should check the given dates for BOOK_DT1 and BOOK_DT2 should not come into the dates present in the table.
For EX: insert into table_name('28-SEP-17','12-NOV-17','XYX'); should throw an error, because '28-SEP-17' comes in between 22-SEP-17 and 12-OCT-17.
you can achive this using the after insert trigger. Somting like this:
create or replace trigger date_check_trg
after insert
on your_table_name
v_cnt NUMBER;
begin
select count(*)
into v_cnt
from you_table_name t1
join you_table_name t2
on ( t1.BOOK_DT1 between t2.BOOK_DT1 and t2.BOOK_DT1
or t1.BOOK_DT2 between t2.BOOK_DT1 and t2.BOOK_DT1
)
AND t1.rowid != t2.rowid
;
if v_cnt > 0 then
raise_application_error(-20999, 'intersection error');
end if;
end;
/
If you want to check before the insert then BEFORE INSERT should be what you're looking for. If the name doesn't matter and your new values should not be between any values previously inserted in the table then this is how I would write it:
create or replace trigger trg_date_chk
before insert
on your_table_name for each row
declare
lnCnt NUMBER;
begin
select count(*)
into lnCnt
from you_table_name a
where :new.BOOK_DT1 < a.BOOK_DT1
and :new.BOOK_DT2 > a.BOOK_DT2;
if lnCnt > 0 then
raise_application_error(-20999, 'INSERT ERROR HERE');
end if;
end;
/

How to insert data into table. But table creating at runtime, is it possible or not?

create or replace procedure p1(p_deptno in number)
is
type t is table of emp%rowtype
index by binary_integer;
v_emp t;
begin
execute immediate 'create table test as select * from emp where ename is null';
select * bulk collect into v_emp from emp where deptno=p_deptno;
for i in v_emp.first..v_emp.last
loop
insert into test(empno, ename, job, mgr, hiredate, sal, comm, deptno) values (v_emp(i).empno, v_emp(i).ename, v_emp(i).job, v_emp(i).mgr, v_emp(i).hiredate, v_emp(i).sal, v_emp(i).comm, v_emp(i).deptno);
end loop;
exception
when value_error then
dbms_output.put_line('Give proper deptno');
end p1;
/
I am getting this error:
PL/SQL: SQL Statement ignored
PL/SQL: ORA-00942: table or view does not exist
If you're creating the table at runtime you must use dynamic SQL to perform the insert. Another problem is that you're sucking that data into an in-memory table, then performing the inserts one-by-one. I suggest something like:
create or replace procedure p1(p_deptno in number) is
type t is table of emp%rowtype
index by binary_integer;
v_emp t;
begin
execute immediate 'create table test as select * from emp where ename is null';
execute immediate
'insert into test
select *
from emp
where deptno = :1' USING p_deptno;
exception
when value_error then
dbms_output.put_line('Give proper deptno');
end p1;
You can still do the inserts one-by-one using dynamic SQL, but it seems like a waste of time and effort.
Best of luck.
When creating a stored procedure in Oracle all referenced objects (in your case table "test" and "emp") in that stored procedure are checked in advance at compile time for appropriate grants/ownership of the referenced object. As the object "test" doesn't exist at compile time there naturally aren't any appropriate grants/ownership for the owner of the stored procedure and you get the error that you specified. To remedy the situation you can put the insert statement in "EXECUTE IMMEDIATE" statement just as #Bob Jarvis stated , this way the check doesn't happen at compile time but at run time and the check succeedes because the object is created at run time and exists along with appropriate grant/ownership.
execute immediate 'insert into test select * from emp where deptno = '|| p_deptno;

Oracle create trigger error (bad bind variable)

I at trying to create trigger with the following code.
CREATE OR REPLACE TRIGGER MYTABLE_TRG
BEFORE INSERT ON MYTABLE
FOR EACH ROW
BEGIN
select MYTABLE_SEQ.nextval into :new.id from dual;
END;
I am getting error
Error(2,52): PLS-00049: bad bind variable 'NEW.ID'
Any ideas? Thanks.
It seems like the error code is telling you there's no such column ID in your table...
Somehow your environment is treating your code as SQL instead of a DDL statement. This works for me (running in sqlplus.exe from a command prompt):
SQL> create sequence mytable_seq;
Sequence created.
SQL> create table mytable (id number);
Table created.
SQL> CREATE OR REPLACE TRIGGER MYTABLE_TRG
2 BEFORE INSERT ON MYTABLE
3 FOR EACH ROW
4 BEGIN
5 select MYTABLE_SEQ.nextval into :new.id from dual;
6 END;
7 /
Trigger created.
Note the trailing "/" - this might be important in the application you are compiling this with.
if one would use proper naming convention the spotting of this type
of errors would be much easier ( where proper means using pre- and postfixes )
for generic object names hinting about their purpose better
i.e. something like this would have spotted the correct answer
--START -- CREATE A SEQUENCE
/*
create table "TBL_NAME" (
"TBL_NAME_ID" number(19,0) NOT NULL
, ...
*/
--------------------------------------------------------
-- drop the sequence if it exists
-- select * from user_sequences ;
--------------------------------------------------------
declare
c int;
begin
select count(*) into c from user_sequences
where SEQUENCE_NAME = upper('SEQ_TBL_NAME');
if c = 1 then
execute immediate 'DROP SEQUENCE SEQ_TBL_NAME';
end if;
end;
/
CREATE SEQUENCE "SEQ_TBL_NAME"
MINVALUE 1 MAXVALUE 999999999999999999999999999
INCREMENT BY 1 START WITH 1
CACHE 20 NOORDER NOCYCLE ;
-- CREATE
CREATE OR REPLACE TRIGGER "TRG_TBL_NAME"
BEFORE INSERT
ON "TBL_NAME"
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
DECLARE
tmpVar NUMBER;
BEGIN
tmpVar := 1 ;
SELECT SEQ_TBL_NAME.NEXTVAL INTO tmpVar FROM dual;
:NEW.TBL_NAME_ID := tmpVar;
END TRG_TBL_NAME;
/
ALTER TRIGGER "TRG_TBL_NAME" ENABLE;
-- STOP -- CREATE THE TRIGGER
If you're like me and your code should be working, try dropping the trigger explicitly before you re-create it. Stupid Oracle.

Displaying command entered to run the trigger

I am trying to figure out how to display the insert statement, which the user enters. I want it to display after the "Please update the insert statement" text prints. From reading a ton of things online, I found out that you can display the previous command entered on oracle, by entering a "/" sign, and also by running this query 'SELECT * FROM gv$sql WHERE SQL_ID = IDENT_CURRENT('gv$sql')'. I tried using an execute immediately statement in the trigger, using dbms_output.put_line(/), and simply using t0_char('/'); in the query as you see below. Any tips?
set serveroutput on
CREATE or REPLACE trigger before_insert_t
before insert on reservations
for each row
DECLARE
rooms_remaining number(5,2);
BEGIN
select roooms_rem into rooms_remaining from reservations where roomno=:new.roomno;
if rooms_remaining = 0 then
dbms_output.put_line('Insertion now allowed because room ' || :new.roomno || ' is booked!' );
dbms_output.put_line('Please update the insert statement');
-- to_char('/');
dbms_output.put_line('insert into reservations values ' || :new.roomno );
-- EXECUTE IMMEDIATE sql_stmt;
end if;
END;
/
show errors
insert into reservations values (99,9);
CREATE or REPLACE trigger before_insert_t
before insert on TEST_TAB1
for each row
DECLARE
sql_insert varchar2(1000);
BEGIN
select sql_text into sql_insert
from (select sql_text
from v$sql
where upper(sql_text) like 'INSERT INTO TEST_TAB1%'
order by first_load_time desc)
where rownum=1;
dbms_output.put_line('Inserting into table SQL is '||sql_insert);
END;
/
SQL> set serveroutput on
SQL> insert into TEST_TAB1 values ('Hello');
1 rows inserted.
Inserting into table SQL is insert into TEST_TAB1 values (:"SYS_B_0")
You order by FIRST_LOAD_TIME and sort it in descending order and select the first row which would be the most recent INSERT statement in case there are multiple INSERTs.

Resources