Skip to Main Content
  • Questions
  • Executing AFTER row trigger on failed insert

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Alla.

Asked: May 17, 2000 - 2:41 pm UTC

Last updated: May 08, 2007 - 10:44 am UTC

Version: 8.1.6

Viewed 10K+ times! This question is

You Asked

I always believed that AFTER triggers are being executed after
integrity constraints are checked. For example, if I am inserting
a row and FK is violated (parent key is not found), then the
statement will fail and "after row" trigger won't be fired.

I just upgraded to 8.1.6. and my application started behaving
weird - the after row triggers are being fired even if the
statement fails (Constraints are immediate and not deferred and
I do get ORA-02291)

Is it something new in 8.1.6? How do I deal with it? How do I
stop these triggers from firing.

You can say, so what if they are firing, the transcation will
be rolled back anyway. This is true, but as a part of the trigger
I am calling an external procedure to do someting and it's
definitely not reversable, even if the statement rolls back.

Please, help. It's a very urgent problem for me

Thank you very much for giving this your attention.


and Tom said...

It doesn't have to work like this (the way you describe above) --the EARLIEST a referential constraint might be able to be verified would be right after the "before for each row" and before the "after for each row" but there are many times when it cannot be verfied at that time.

The documentation actually says:

<quote concepts manual, v7.3, section in "the mechanisms of constraint checking">

• A multiple row INSERT statement, such as an INSERT statement
with nested SELECT statement, can insert rows that reference
one another. For example, the first row might have EMPNO as
200 and MGR as 300, while the second row might have EMPNO
as 300 and MGR as 200.

Each case reveals something about how and when Oracle performs
constraint checking.

The first case is easy to understand; a null is given for the foreign key value. Because nulls are allowed in foreign keys, this row is inserted successfully into the table.

The second case is more interesting. This case reveals when Oracle effectively performs its constraint checking: after the statement has been completely executed. To allow a row to be entered with the same values in the parent key and the foreign key, Oracle must first execute the statement (that is, insert the new row) and then check to see if any row in the table has an EMPNO that corresponds to the new row’s
MGR.
</quote>


the relevant part of this is:

This case reveals when Oracle effectively performs its constraint checking: after the statement has been completely executed.

I agree that in earlier releases, it would *sometimes* not fire the after, for each row trigger but that was never gauranteed or promised (quite the contrary actually). Here is an example showing it could fire the AFTER for each row trigger before checking constraints in earlier releases:

tkyte@ORA734.WORLD> create table c ( a int primary key,
x references c(a) );

Table created.

tkyte@ORA734.WORLD> create or replace trigger c_bi
before insert on c
2 begin
3 dbms_output.put_line( 'Before insert' );
4 end;
5 /
Trigger created.

tkyte@ORA734.WORLD> create or replace trigger c_bifer
before insert on c for each row
2 begin
3 dbms_output.put_line( 'Before insert FER '
|| :new.a || ' ' || :new.x );
4 end;
5 /
Trigger created.


tkyte@ORA734.WORLD> create or replace trigger c_aifer
after insert on c for each row
2 begin
3 dbms_output.put_line( 'Before after FER ' ||
:new.a || ' ' || :new.x );
4 end;
5 /
Trigger created.

tkyte@ORA734.WORLD> create or replace trigger c_ai
after insert on c
2 begin
3 dbms_output.put_line( 'After insert ' );
4 end;
5 /
Trigger created.

tkyte@ORA734.WORLD> set serveroutput on
tkyte@ORA734.WORLD> insert into c
2 select 1, 2 from dual
3 union all
4 select 2, 3 from dual
5 /
Before insert
Before insert FER 1 2
Before after FER 1 2
Before insert FER 2 3
Before after FER 2 3
insert into c
*
ERROR at line 1:
ORA-02291: integrity constraint (TKYTE.SYS_C00382) violated - parent key not found

See - it fired the AFTER FOR EACH ROW trigger 2 times before checking the consraint -- and that was release 7.3.


Back to the root problem -- your extproc. That is not transactional (the maintaining of the external data source). You should be aware that just because something makes it past the AFTER FOR EACH ROW trigger does not mean it'll be in the table!!!! Regardless of trigger firing order/RI or anything.

What about:

insert into T select * from really_big_table;

and you run out of space in the middle? or the server crashes? or the end user rolls back? half of the rows will make it out, half will not. the half that made it out will get rolled back and won't exist -- you have a mess.

Here is what I would do in your case.

1) my after, for each row trigger would queue a job in dbms_job to be executed right after I commit and the changes are permanent.

2) this job it queues would simply be a call to the procedure its calling in real time right now.

3) set up the job queues to check for jobs to run every 15 seconds or so.

that way -- its transactional -- if I commit - the extproc gets called. If I get rolled back (for WHATEVER reason and there are dozens that could affect you -- regardless of RI or not), then the jobs themselves will get removed from the queue. The rows will be propagated in the background --probably providing a better experience for the end user.

how does that sound? I use dbms_job for lots of stuff to make it transactional (like sending emails and such). I queue the request to perform the operation -- I do not perform the operation I cannot rollback. Very safe, very efficient, much more robust.


Rating

  (6 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

How can I pass status back from a trigger code??

Ravi Kumar, March 11, 2004 - 10:49 am UTC

Hi Tom,

We have an application in which the user selects a text file from his local client machine from the client application and makes a procedure call to the Oracle Database for each line of the file. The procedure inserts those values to a staging table. There is a "after insert each row" trigger on the staging table where some validations are done and updates/inserts someother main tables. If the validations fails, then trigger code raises an exception. Based on the exception, the procedure retuns the status to the client.

The reason for coding the trigger on the staging table is, the end user wants to see the progress of the file read and processing of the data.

Now, when the validations fail and the exception is raised, the orginal insert to the staging table is also not finished. How to handle this situation, if we want to make the insert into staging table happen and return the fail validation status back to the procedure and hence to the client? Or any other better solution to this situation.

Thanks in advance,
Ravi

Tom Kyte
March 11, 2004 - 2:05 pm UTC

I don't see why this is a trigger. why is this a trigger? for the end user to see progress doesn't need a trigger here.

sounds like procedure wants to

a) insert row REGARDLESS
b) do the code you currently have in trigger to figure out what message to return to client.

the trigger would only be used to "protect" the table from getting bad data inserted.

What would be a possible solution?

Ajeet, January 26, 2005 - 5:11 am UTC

Tom,
I have 2 tables on a oracle instance evnodsd1 
they are like this

create table t1 (id number(9) not null , name varchar2(1) not null) ;
alter table t1 add constraint t1_pk primary key(id)
using index ;

create table t2 (dtl_id number(9) not null,id number(9) not null,dtl_name varchar2(3) not null) ;
alter table t2 add constraint t2_fk1 foreign key(id)
references t1(id) ;
alter table t2 add constraint t2_pk primary key(dtl_id,id)
using index ;

so t1 is parent of t2.

table t2 is populated using a pro*c program.

then I have a table t2_qa on a different oracle instance evnrdwq2  -- and I have a db link from evnodsd1 to this instance.

I have a trigger at table t2 of instance evnodsd1 and this trigger will populate t2_qa --code is like this

create or replace trigger t2_trg
before insert 
on t2
referencing new as new old as old
for each row
--
declare
l_module varchar2(1024) ;
l_where varchar2(1024) ;
l_cnt1  number ;
l_cnt2  number ;
tran_exception exception ;
pragma exception_init(tran_exception,-02055) ;
--
begin
--
l_module := who_am_i ;
--
/* select count(*) into l_cnt1  from t1 where nvl(id,-1) = nvl(:new.id,-1)  ;
if l_cnt1 <> 0 then */

    insert into ops$rdetl.t2_qa@evnrdwq2
      (
      dtl_id ,id,name)
     values (:new.dtl_id,:new.id,:new.dtl_name) ;
--end if ;
exception
when others then
rdo_log_error(l_module) ;
raise ;
end ;
/

so this inserts a row into t2_qa as after it is inserted into t2.

when the source program (pro*C) inserts a bad row such a row for which there is no partent record..I get 
ORA-02055 and ORA-02067..I read about this..I know it is coming because  there was a FK voilation during the insert into t2.

I have a workaround like this...that I check that if parent record exist in t1 for t2 then only insert..into t2_qa (in the trigger code itself).
(please see the commented code --that is my workaround for now --it does not look too good).

My question is --is there any other way to handle or resolve this problem -- How can my trigger know that --execute or fire only if the transaction on t2 was commited successfuly -- any oracle supplied solution -- Any thing neat than this one.i saw a mention of this in oracle server manual but could not actually get it working..as I am confused conceptually -- the hang up is  -- How to know that the insert on the source table was succesful or not and how would my trigger will know this.

As this trigger will be executed millions times in a day.(in worst case -- average is less than million times).

also I have 7-8 such triggres and I want to do it in a better way --if any.

Any help would be great. 

below are the insert statement I tried and the error i got.

insert into t1 values (1,'A') ;
commit ;

then when I tried to insert a row in t2..I got this error..

SQL> SQL>
SQL> insert into t2 values (1,2,'BB') ;
insert into t2 values (1,2,'BB')
*
ERROR at line 1:
ORA-02055: distributed update operation failed; rollback required
ORA-02291: integrity constraint (OPS$RDETL.T2_FK1) violated - parent key not
found

 

Tom Kyte
January 26, 2005 - 8:59 am UTC

why does it matter? who cares -- just raise an error. the insert isn't going to happen, why does it matter who raises it??

(and why are you trying to do syncronous replication -- yuck, fairly horrible stuff making it so your systems are really never available)

could not be more agree with you but..

Ajeet, January 26, 2005 - 10:54 am UTC

Tom,
I knew you will ask it ..yes the insert will not happen but the source program was written in pro*c before 8 years and they handle exceptions in strange way -- like if they get -2291 they do insert the parent records..but now because this new trigger they first get ora-2055 and so there process gets aborted..exit.I do not have a say in it --as customer don't want to change it.so honestly i want to do as good as I can think of.
so blame is on me..so I have trying to find a better way.

Any ideas -- most appreciated.

Thanks again
Ajeet

Tom Kyte
January 26, 2005 - 11:20 am UTC

don't syncronously replicate then, use snapshots (materialized views). better uptime, better availability, easier to manage

Agreed But..

Ajeet, January 26, 2005 - 11:50 am UTC

Tom,
Yes..I Should use the MV's or Stream ..but they have so much of application code which is pointing to these existing tables -- so hard to change all such tables.
I know it does not sound logical and even I am not convineced..but I am just another memeber in the project team--so back to my question -- in the given problem --can I do anything else --that is can i avoid that problem because of the trigger..Code remains same as I have given above.sorry if i am trying to be persistent on taking a wrong approach..but I want to learn a way to resolve this problem if any.

Thanks alot
Ajeet

Tom Kyte
January 26, 2005 - 1:19 pm UTC

not following you.

You create mv logs on the tables in questions (instead of hand coding a trigger, one that is not actually working we might add)

You create materialized views on the other site (instead of create table)

and there you code.

what tables "have to change"?

The Source table and Target table in question..

Ajeet, January 27, 2005 - 7:43 am UTC

Tom.
Both the source table on which I have a trigger and the target table -- which is being populated from Trigger ..all of these are in the production ..and I am trying to fix the trigger error which I have described in my above post..so I was trying to say the replacing the target table will not be easy for me as there are too many stakeholders ..so I want to fix the trigger so that it can work as described above.
I was trying to use your very first response in this thread --that is use dbms_job ..it sounds very cool except that my users want the data in target table immedietly --that is right after it comes to t2.
I know there are several ways -- i could have used stream..i could have used MV with refresh group and autocommit..my constraint is - I am allowed to change just the tiny part-- the trigger.
I am still in search of an answer/help.

Thanks
Ajeet

Tom Kyte
January 27, 2005 - 9:51 am UTC

I don't think you'll "get there from here"

the production code is broke
the fix is rather simple

syncronous replication is really bad.

sorry, don't know what else to say.

OK

Kumar, May 07, 2007 - 2:46 am UTC

Hi Tom,
Please check the below trigger.
This is only a sample test case.

Requirement is simple and it is
whatever rows that are inserted into table t with value x = 3
must be inserted into t_copy and ** when clause is mandatory **.
I am getting errors with this trigger.How to correct this?



SQL> create table t(x number,y date)
2 /

Table created.

SQL> create table t_copy as select * from t
2 /

Table created.

SQL> create or replace trigger ins_t_copy
2 after insert on t
3 for each row
4 when ( x = 3 )
5 begin
6 insert into t_copy select * from t;
7* end;
SQL> /
when ( x = 3 )
*
ERROR at line 4:
ORA-04076: invalid NEW or OLD specification

Tom Kyte
May 08, 2007 - 10:44 am UTC

when new.x


I generally find the documentation to be of great help in cases like this :)

http://docs.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_7004.htm#sthref7994

...
WHEN Clause

Specify the trigger condition, which is a SQL condition that must be satisfied for the database to fire the trigger. See the syntax description of condition in Chapter 7, "Conditions". This condition must contain correlation names and cannot contain a query.

The NEW and OLD keywords, when specified in the WHEN clause, are not considered bind variables, so are not preceded by a colon (:). However, you must precede NEW and OLD with a colon in all references other than the WHEN clause.

.........

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library