I have After Insert/Update trigger on Table T1 which get the referential data for Col1 from T2 and does some work and insert it into another table.
The col1 is FK to Table T2.
When user insert the incorrect or non existing value into the Col1 and if trigger is disabled I am getting constraint error that is fine.
But when trigger is enabled and user insert the wrong value in Col1 trigger is getting fired and shows the 'no data found' error message.
Actually I am expecting the table to throw constraint error, but trigger is throwing it.
Please let me know your comments about this trigger behaviour.
You do not mention whether you are using BEFORE or AFTER triggers. Please check the documentation for the order of execution:
BEFORE statement
BEFORE row
CONSTRAINTS
AFTER row
AFTER statement
I'm guessing the trigger would have to be a BEFORE trigger. It will run prior to the constraints being checked. If the trigger raises NO_DATA_FOUND, then the constraint never gets checked. If the trigger is disabled, it is not run, so the constraint gets checked.
Related
I am learning triggers using Oracle 12c HR sample schema and Oracle Develop PL/SQL Program Units student guide. Oracle says that the trigger execution model is:
Execute all BEFORE STATEMENT triggers.
Loop for each row affected by the SQL statement:
Execute all BEFORE ROW triggers for that row.
Execute the DML statement AND PERFORM INTEGRITY CONSTRAINT CHECKING FOR THAT ROW.
Execute all AFTER ROW triggers for that row.
Execute all AFTER STATEMENT triggers.
The sample HR schema has an EMPLOYEES table with a DEPARTMENT_ID FK recerencing a DEPARTMENT_ID PK in a DEPARTMENTS table.
Correctly, if I try to insert a record in EMPLOYEES having a FK value that does not exist among the PKs in DEPARTMENTS, I will get a ORA-02291: integrity constraint (HR.EMP_DEPT_FK) violated - parent key not found error.
As a proof of concept, I create the following BEFORE UPDATE trigger to insert the missing record in DEPARTMENTS and avoid the integrity constraint violation:
CREATE OR REPLACE TRIGGER employee_dept_fk_trg
BEFORE UPDATE OF department_id ON employees
FOR EACH ROW
BEGIN
INSERT INTO departments (department_id, department_name) VALUES(:new.department_id,'Dept '||:new.department_id);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
NULL; -- mask exception if department already exists
END;
It works fine (I can insert a non existent FK as the corresponding PK would be created first).
The studying material says then that we could make the HR.EMP_DEPT_FK constraint DEFERRABLE INITIALLY DEFERRED in order to postpone the integrity check at the moment of the final COMMIT and not at the end of each INSERT statement (see item #4 in list), and so create an AFTER UPDATE trigger rather than a BEFORE UPDATE one like the previous.
Are there any advantages of doing so versus just using a BEFORE UPDATE trigger?
Before altering the HR.EMP_DEPT_FK constraint setting it DEFERRABLE INITIALLY DEFERRED, I wanted to verify the assertion in item #4, so I created an identical AFTER UPDATE trigger and I tried to insert a non existent FK in the employees table: I RECEIVED NO ORA-02291 ERROR and the new PK record in DEPARTMENTS table has been created! It seems that the AFTER UPDATE trigger executes before any integrity constraint check, while that should happen after! (see item #5)
Could you help me on this topic, please?
Thank you all,
Pino
let's say there are tables a and b.
There is an AFTER INSERT trigger on a, that copies the row data to b.
If, during the execution of the trigger, there is an error does it impact table a in any way?
If the trigger does not compile, because it's ill-defined, does it impact table a?
I want to add a trigger to a table that is 'not mine'. I want to evaluate the risk that this can potentially pose.
Cheers
============== edit ================
I verified that - by handling the error (as suggested in the reply) - it now does not impact the base table.
create table tableA (column1 number);
create table tableB (column1 number, CONSTRAINT constraintName PRIMARY KEY (column1));
create or replace TRIGGER tableA_trigger
AFTER INSERT ON tableA
FOR EACH ROW
DECLARE
--
BEGIN
insert into tableB values (1);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error occured but ignored.');
END;
insert into tableA values (1);
insert into tableA values (1);
After that, tableA had two records, tableB only 1.
Without the exception handling, both tables would only have one record each, and after the first insert it would have shown an exception in the SQL Developer window.
Yes, it will impact the base table.
If there is error in insert trigger it will not allow to insert any record in base table. Same applies to all type of triggers.
Also, Adding DML triggers to tables affects the performance of DML statements on those tables.
According to oracle documentation:
If a predefined or user-defined error condition or exception is raised
during the execution of a trigger body, then all effects of the
trigger body, as well as the triggering statement, are rolled back
(unless the error is trapped by an exception handler). Therefore, a
trigger body can prevent the execution of the triggering statement by
raising an exception. User-defined exceptions are commonly used in
triggers that enforce complex security authorizations or integrity
constraints.
I'm on Oracle Express 11g. I'm trying to create this trigger:
CREATE TRIGGER remove_useless_surveys
BEFORE UPDATE OF agent_id, agency_id, province_id ON surveys
FOR EACH ROW WHEN (
new.agent_id IS NULL AND
new.agency_id IS NULL AND
new.province_id IS NULL)
DELETE FROM surveys WHERE survey_code = :new.survey_code
agent_id, agency_id and province_id are foreign keys, with ON DELETE SET NULL clause.
I need a row in the surveys table to be DELETED when all the three foreign keys are set to NULL (because the referred rows are deleted).
The trigger compiles with no problems, but when the condition fires, then I get this error:
SQL error: ORA-04091: table REALESTATE.SURVEYS is mutating,
trigger/function may not see it ORA-06512: at
"REALESTATE.REMOVE_USELESS_SURVEYS", line 1 ORA-04088: error during
execution of trigger 'REALESTATE.REMOVE_USELESS_SURVEYS'
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
Actually, I know I'm editing the same table that fired the trigger. But I want to cancel the update, or finalize it, I don't care, and then delete that row.
Can you help me? How can I achieve what I'm trying to do?
You could use a statement level trigger like this:
CREATE TRIGGER remove_useless_surveys
AFTER UPDATE OF agent_id, agency_id, province_id ON surveys
BEGIN
DELETE FROM surveys
WHERE agent_id IS NULL
AND agency_id IS NULL
AND province_id IS NULL;
END;
I have a trigger that is fire on inserting a new record in table in that i want to insert new record in the same table.
My trigger is :
create or replace trigger inst_table
after insert on test_table referencing new as new old as old
for each row
declare
df_name varchar2(500);
df_desc varchar2(2000);
begin
df_name := :new.name;
df_desc := :new.description;
if inserting then
FOR item IN (SELECT pid FROM tbl2 where pid not in(1))
LOOP
insert into test_table (name,description,pid) values(df_name,df_desc,item.pid);
END LOOP;
end if;
end;
its give a error like
ORA-04091: table TEST_TABLE is mutating, trigger/function may not see it
i think it is preventing me to insert into same table.
so how can i insert this new record in to same table.
Note :- I am using Oracle as database
Mutation happens any time you have a row-level trigger that modifies the table that you're triggering on. The problem, is that Oracle can't know how to behave. You insert a row, the trigger itself inserts a row into the same table, and Oracle gets confused, cause, those inserts into the table due to the trigger, are they subject to the trigger action too?
The solution is a three-step process.
1.) Statement level before trigger that instantiates a package that will keep track of the rows being inserted.
2.) Row-level before or after trigger that saves that row info into the package variables that were instantiated in the previous step.
3.) Statement level after trigger that inserts into the table, all the rows that are saved in the package variable.
An example of this can be found here:
http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936
Hope that helps.
I'd say that you should look at any way OTHER than triggers to achieve this. As mentioned in the answer from Mark Bobak, the trigger is inserting a row and then for each row inserted by the trigger, that then needs to call the trigger to insert more rows.
I'd look at either writing a stored procedure to create the insert or just insert via a sub-query rather than by values.
Triggers can be used to solve simple problems but when solving more complicated problems they will just cause headaches.
It would be worth reading through the answers to this duplicate question posted by APC and also these this article from Tom Kyte. BTW, the article is also referenced in the duplicate question but the link is now out of date.
Although after complaining about how bad triggers are, here is another solution.
Maybe you need to look at having two tables. Insert the data into the test_table table as you currently do. But instead of having the trigger insert additional rows into the test_table table, have a detail table with the data. The trigger can then insert all the required rows into the detail table.
You may again encounter the mutating trigger error if you have a delete cascade foreign key relationship between the two tables so it might be best to avoid that.
I am using:
set constraints all deferred;
(lots of deletes and inserts)
commit;
This works as expected. If there are any broken relationships then the commit fails and an error is raised listing ONE of the FKs that it fails on.
The user fixes the offending data and runs again. then hits another FK issue and repeats the process.
What I really want is a list of ALL FKs in one go that would cause the commit to fail.
I can of course write something to check every FK relationship through a select statement (one select per FK), but the beauty of using the deferred session is that this is all handled for me.
The set immediate answer worked fine for me.
Taking the A,B,C example from the other post:
SQL> create table a (id number primary key);
Table created.
SQL> create table b (id number primary key, a_id number, constraint fk_b_to_a foreign key (a_id) references a deferrable initially immediate);
Table created.
SQL> create table c (id number primary key, b_id number, constraint fk_c_to_b foreign key (b_id) references b deferrable initially immediate);
Table created.
SQL> insert into a values (1);
1 row created.
SQL> insert into b values (1,1);
1 row created.
SQL> insert into c values (1,1);
1 row created.
SQL> commit;
Commit complete.
I have a consistent set of data.. my starting point.
Now I start a session to update some of the data - which is what I was trying to describe in my post.
SQL> set constraints all deferred;
Constraint set.
SQL> delete from a;
1 row deleted.
SQL> delete from b;
1 row deleted.
SQL> insert into b values (10,10);
1 row created.
SQL> set constraint fk_b_to_a immediate;
set constraint fk_b_to_a immediate
*
ERROR at line 1:
ORA-02291: integrity constraint (GW.FK_B_TO_A) violated - parent key not foun
SQL> set constraint fk_c_to_b immediate;
set constraint fk_c_to_b immediate
*
ERROR at line 1:
ORA-02291: integrity constraint (GW.FK_C_TO_B) violated - parent key not foun
Which tells me about both broken constraints (C to B) and (B to A), without rolling back the transaction. Which is exactly what I wanted.
Thanks
First option, you can look into DML error logging. That way you leave your constraints active, then do the inserts, with the erroring rows going into error tables. You can find them there, fix them, re-insert the rows and delete them from the error table.
The second option is, rather than trying the commit, attempt to re-enable each deferred constraint dynamically, catching the errors. You'd be looking at a PL/SQL procedure that queries ALL_CONSTRAINTS for the deferrable ones, then does an EXECUTE IMMEDIATE to make that constraint immediate. That last bit would be in a block with an exception handler so you can catch the ones that fail, but continue on.
Personally, I'd go for option (1) as you do get the individual records that fail, plus you don't have to worry about deferrable constraints and remembering to make them immediate again after this script so it doesn't break something later.
I guess somewhere in memory Oracle must be maintaining a list, at least of constraints that failed if not the actual rows. I'm pretty confident it doesn't rescan the whole set of tables at commit time to check all the constraints.
Oracle's Error Mechanism is too primitive to return a collection of all errors that COULD occur. I mean, it's a cool thought but think about what you'd have to do if you wrote the code. Your standard error handling would need to be thwarted. Instead of returning an error as soon as you encounter it, you'd have to continue somehow to see if you could proceed if the first error you caught wasn't an error at all. And even if you did all of this you'd still face he possibility that the row you add that fixes the first error actually causes the second error.
For example:
A is the parent of B is the parent of C. I defer and I insert C. I get an error that there's no B record parent. I defer and I add B. Now I get another error that there's no A.
This is certainly possible and there's no way to tell in advance.
I think you're looking for the server to do some work that's really your responsibility. At the risk of mod -1, you're using a technique for firing an automatic weapon called "Spray and Pray" -- Fire as many rounds as possible and hope you kill the target. That approach just can't work here. You disable your RI and do a bunch of steps and hope that all the RI works out in the end when the database "grades" your DML.
I think you have to write the code.