Reading current table id during trigger operation - oracle

I have three tables: A and B
The relationship is A can have many B
So B has a reference to the A.id as one of its columns
Table A
|id|date|...
Table B
|id|A_id|...
I have created an Oracle trigger on table A so that when it is updated it updates an A_Mod table.
This trigger is
CREATE OR REPLACE TRIGGER TR_A_INSERT_UPDATE
AFTER INSERT OR UPDATE ON A
FOR EACH ROW
BEGIN
INSERT INTO A_Mod values(..., :new.date, ...)
END;
This works fine :)
My problem si creating the trigger for table B.
The trigger is:
CREATE OR REPLACE TRIGGER TR_B_INSERT_UPDATE
AFTER INSERT OR UPDATE ON B
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT aa.date INTO ts FROM B bb
INNER JOIN A aa ON a.id = bb.A_id
WHERE bb.id = :new.id;
INSERT INTO A_Mod values(..., :new.date, ...)
END;
This trigger is reading the ID of the updated line in table B and then getting the date from the corresponding row in table A. It then tries to insert it into A_Mod
The problem is I get a mutating error
Error report:
SQL Error: ORA-04091: table B is mutating, trigger/function may not see it
ORA-06512: at "TR_B_INSERT_UPDATE", line 5
ORA-04088: error during execution of trigger 'TR_B_INSERT_UPDATE'
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.
Looking at the docs I can remove this error by removing the FOR EACH ROW line and having the trigger fire once per statement rather than once per row. Unfortunately I am using an ORM mapper so do not control how the updates happen. I think there would be times when the update could cover multiple rows.
The docs say something about creating a temporary table but I am not sure how this would help. Would I have to create a temporary table inside the trigger, create a trigger on this temporary table which then updates A_Mod, update this temporary table when the trigger is fired and then delete everything after?
Any tips greatly appreciated.
Thanks

It doesn't appear that there is any reason in your trigger on B to query the B table. You should be able to simply query A using the :new.a_id
CREATE OR REPLACE TRIGGER TR_B_INSERT_UPDATE
AFTER INSERT OR UPDATE ON B
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT a.date
INTO ts
FROM A a
WHERE a.id = :new.a_id;
INSERT INTO A_Mod values(..., :new.date, ...)
END;
From a data modeling standpoint, however, I would be very concerned if a trigger on a child table needed to query any information from the parent table or to insert data into the same history table that the parent table writes to. That seems likely to indicate a normalization issue.

Related

If a trigger on an Oracle DB table fails to execute successfully or does not compile, can it impact the base table?

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.

Mutating table exception is not occurring

I have created a trigger and was expecting a mutating table error in below case but didn't get one through normal insert but getting an error while inserting using a query. I am not sure which concept I am missing here.
drop table temp;
create table temp (id number,name varchar2(500),is_active number);
create or replace trigger temp_trg before insert on temp
for each row
declare
v_count number;
begin
select count(1) into v_count from temp;
update temp set is_active=0 where is_active=1 and id=:new.id;
end;
/
select * from temp;
insert into temp values (1,'xyz',1);
insert into temp values (1,'xyz',1);
insert into temp select 1,'xyz',1 from dual;
getting an error while inserting using a query.
Mutating table occurs when we query the table which owns the trigger. Specifically it happens when Oracle can't guarantee the outcome of the query. Now when you insert a single row into the table Oracle can predict the outcome of the query when the FOR EACH ROW trigger fires, because it's a single row.
But with an INSERT FROM query Oracle is confused: should the count be the final figure including all the rows selected by the query or just a rolling count? The answer seems straightforward in this case but it's easy to imagine other queries where the answer is not clear cut. Rather than evaluate each query on its predictability Oracle enforces a plain fiat and hurls ORA-04091 for all query driven inserts.
The restriction on Mutating Tables applies to all triggers that use FOR EACH ROW clause except when either of the following is true:
the trigger fires on BEFORE event, i.e. the data wasn't actually changed
it is known that only one row will be affected - INSERT ... VALUES is the only DML that meets this condition

Creating a back up table trigger in Oracle

I have a table A which is constantly updated ( insert statements ) by one application. I want to create another table B which consists only few columns of table A. For this I thought of creating a trigger which triggered after insert on Table A, but I don't know how should I write the insert statement inside the trigger. I am not a database expert, so may be I am missing something simple. Please help.
Here is my trigger code:
CREATE OR REPLACE TRIGGER "MSG_INSERT_TRIGGER"
AFTER
insert on "ACTIVEMQ_MSGS"
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
begin
execute immediate 'truncate table MSG_BACKUP';
insert into MSG_BACKUP select * from ACTIVEMQ_MSGS;
COMMIT;
end;
This does not seem to be good idea: every time a new record gets inserted into Table A, delete everything from table B and copy all records from table A. This will be a huge performance issue, once there is many records in table A.
Wouldn't it be enough to create a view on the desired columns of table A?
If not, that is, you still want to "log" all inserts into an other table, then here you go. (I suppose you want to copy the following fields: f1, f2, f3)
CREATE OR REPLACE TRIGGER TR_AI_ACTIVEMQ_MSGS
AFTER INSERT ON ACTIVEMQ_MSGS
FOR EACH ROW
BEGIN
INSERT INTO MSG_BACKUP (f1, f2, f3)
VALUES (:new.f1, :new.f2, :new.f3);
END TR_AI_ACTIVEMQ_MSGS;
Inside an Oracle trigger the record values are available in de :new pseudo record. Your insert statement may look like this:
insert into B (column1, column2)
values (:new.tableA_col1,:new.tableA_col2)

Creating an UPDATE trigger that causes the removal of the triggering row

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;

INSERT trigger for inserting record in same table

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.

Resources