How to fix the mutating trigger in oracle - plsql

I wrote the trigger for updating the column value in the same table. For Ex I wrote a trigger on metermaster table after update of assettype column , with in the trigger i am trying to update the instantaneousinterval column in the same metermaster table. Its throws the error like this
ERROR: ORA-04091: table PSEB.METERMASTER is mutating, trigger/function
may not see it.
my trigger code is as follows:
CREATE OR REPLACE TRIGGER PSEB.spiupdate
AFTER
update of assettype
ON pseb.metermaster
referencing new as new old as old
for each row
DECLARE
vassettype number;
resval number(10);
vassettypename varchar2(50);
vmeterid number;
begin
select :new.assettype,:new.meterid INTO vassettype,vmeterid from dual;
select assettypename into vassettypename from pseb.METERASSETINSTTYPE where ASSETTYPEID=vassettype;
select case when assettypename like 'DT' then 86400 when assettypename like 'HT' then 3600 when assettypename like 'FSB' then 86400 end into resval from pseb.meterassetinsttype where assettypename =vassettypename;
update pseb.metermaster set instantaneousinterval=resval where meterid=vmeterid;
end;
I tried to use the
pragma autonomous_transaction;
but it gives the deadlock condition.
ERROR: ORA-00060: deadlock detected while waiting for resource
ORA-06512:
pls help me to fix this issue.

instead of this update statement
update pseb.metermaster set instantaneousinterval=resval where meterid=vmeterid;
use
:new.instantaneousinterval=resval;

A mutating table occurs when a statement causes a trigger to fire and that trigger references the table that caused the trigger. The best way to avoid such problems is to not use triggers, but I suspect the DBA didn’t take the time to do that. He could have done one of the following:
Changed the trigger to an after trigger.
Changed it from a row level trigger to a statement level trigger.
Convert to a Compound Trigger.
Modified the structure of the triggers to use a combination of row and statement level triggers.
Made the trigger autonomous with a commit in it.
Try this pragma autonomous_transaction; with Commit

Since the trigger is updating the same table on which it is defined, why don't you update the two columns in the first update statement itself?
i.e, Instead of using an update like
UPDATE pseb.metermaster
SET assettype = '<v_assettype>';
and relying on trigger to update the instantaneousinterval column, why don't you use an update statement like the following (code is not tested)
UPDATE pseb.metermaster
SET assettype = '<v_assettype>',
instantaneousinterval = (SELECT CASE
WHEN assettypename LIKE 'DT' THEN 86400
WHEN assettypename LIKE 'HT' THEN 3600
WHEN assettypename LIKE 'FSB' THEN 86400
END
FROM pseb.meterassetinsttype
WHERE assettypeid = '<v_assettype>');
In my opinion, using a trigger and autonomous_transaction in this case would be a wrong approach. To know why this is wrong, please search http://asktom.oracle.com/ for this error.

Related

PL/SQL: Run package after checking all rows in trigger

I have an inventory table with expected quantities and actually received quantities. Let's say inv.q_ex and inv.q_rd.
The INSERT to the table has a positive value in q_ex and a zero in q_rd because it hasn't arrived yet. I'd like to run a package when I detect that the q_rd value changes from 0 to something else, indicating it's been received and stored.
Making a trigger to detect after update and checking each row is easy, but I'm not sure how to ensure it only runs once.
The skeleton is:
CREATE OR REPLACE TRIGGER example
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
pkg.proc();
END IF;
END;
/
The problem I see is I only want it to run one time. I just need to identify when it needs to be executed. Ideally, on the first row where my condition is met, I would exit the loop (seems like a waste to keep checking when I already know I need to execute) and call my procedure.
I couldn't find a way to "exit" the for each and treat it as a normal AFTER UPDATE, so then I tried using both BEFORE UPDATE and AFTER UPDATE. The BEFORE portion would check each row and update a boolean. The AFTER portion would wait for that to happen and if it was true, call the procedure.
CREATE OR REPLACE TRIGGER example
BEFORE UPDATE ON inv
FOR EACH ROW
DECLARE
shouldExecute BOOLEAN;
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
shouldExecute := TRUE;
END IF;
END;
AFTER UPDATE ON inv
BEGIN
IF shouldExecute THEN
pkg.proc();
END IF;
END;
/
I suspect this wouldn't work anyway because, according to the syntax, it redeclares the boolean variable on each row. I thought that maybe I could make it "global" but regardless, turns out I can't add both BEFORE and AFTER to the same trigger for some reason (unless I didn't research enough), so I broke it out into two triggers. The problem now is I can't share that boolean between the two triggers. Can I share the value, or am I going about this all wrong?
There's a lot of little questions here so I'll try to answer them all :)
Regarding "FOR EACH ROW", Oracle triggers support two different triggering methods, STATEMENT or ROW. If you include the "FOR EACH ROW" in the definition, you'll get a row trigger, which is fired once per row affected by the query, which is what you seem to want here. Statement level triggers get fired only once for each query. An advantage to using row triggers is that you can use the :OLD and :NEW metavariables which refer to the previous and current row values.
As you've discovered, you can't add BEFORE and AFTER to the same trigger - you'll need to break them out into two triggers.
Unfortunately there isn't a simple way of sharing the boolean variable between the two triggers. The easiest way is probably to create a package with a public variable, which you can set in the BEFORE trigger, and check in the AFTER trigger.
The package would look something like this:
CREATE OR REPLACE PACKAGE PCKG_DEMO
AS
shouldExecute BOOLEAN;
END;
/
Then your BEFORE UPDATE trigger:
CREATE OR REPLACE TRIGGER beforeexample
BEFORE UPDATE ON inv
FOR EACH ROW
BEGIN
IF :OLD.q_rd = 0 AND :NEW.q_rd > 0 THEN
PCKG_DEMO.shouldExecute := TRUE;
END IF;
END;
/
And your AFTER UPDATE trigger:
CREATE OR REPLACE TRIGGER afterexample
AFTER UPDATE ON inv
FOR EACH ROW
BEGIN
IF PCKG_DEMO.shouldExecute THEN
pkg.proc();
END IF;
END;
/
Those are a bit pseudocode-y - I don't have access to an Oracle database right now. You can read more about triggers here. Hope this helps!
You need to do more research. The link above takes you to trigger discussion for Oracle 9. I hope you are not actually using that version; support ended in 2007. Since version 11g Oracle has provided "Compound Triggers" where you can fire the same trigger both Before and After for both statement and row level. Compound triggers do allow sharing variables between the different invocations.

Oracle (11g) compound trigger not updating CLOB data field

I tried searching for answers to this but couldn't really find anything useful. Either there isn't or my searching capabilities took a knock. Regardless, here's my situation:
I have a case where I've got 2 identical tables, Z_TEST and Z_TEST2. Call Z_TEST2 an audit table if you'd like.
Both have 2 columns, ID (number) and CFIELD (CLOB). Z_TEST has a trigger which will either insert or update Z_TEST2 (in effect only the CLOB field).
Scenario 1:
If I create the following trigger on Z_TEST, which is a normal trigger, the audit table gets updated too:
CREATE OR REPLACE TRIGGER Z_TEST_TRIG
AFTER INSERT OR UPDATE ON Z_TEST
FOR EACH ROW
Begin
If inserting then
Begin
insert into Z_TEST2 values(:new.ID, :new.CFIELD);
end;
end if;
if updating then
begin
update Z_TEST2 Set CFIELD = :new.CFIELD where ID = :new.id;
end;
end if;
End;
Scenario 2:
If I create the following trigger which is a compound trigger and make use of its "After each row" block, the audit table's CLOB updates with nothing, null:
create or replace trigger Z_TEST_TRIG
FOR UPDATE OR INSERT ON Z_TEST
COMPOUND TRIGGER
AFTER EACH ROW IS
Begin
If inserting then
Begin
insert into Z_TEST2 values(:new.ID, :new.CFIELD);
end;
end if;
if updating then
begin
update Z_TEST2 Set CFIELD = :new.CFIELD where ID = :new.id;
end;
end if;
END AFTER EACH ROW;
END Z_TEST_TRIG;
Does anyone have any idea why this would work for scenario 1, but not 2? Our WHOLE framework is based on scenario 2 (compound triggers) and I've only recently come across the need to use CLOB data types, and with it came this problem.
Why the difference and is my code missing something?
Having no time to linger on this issue I solved it by making use of a variable.
Declaring a CLOB variable in the declaration section and assigning the value of the :new.clob_field to it either in BEFORE EACH ROW, or AFTER EACH ROW, and using the variable in your insert/update statement rather than :new.clob_field within the trigger solves this issue.
I came across a lot of posts by people battling with this (compound triggers specifically, not simple triggers), so I hope the time i spent on this helps someone else and saves them time.
It would really be helpful to my sanity if anyone comes across this post who knows the reason why :new.clob_field loses its value in a compound trigger when used in insert/update statements in the BEFORE/AFTER each row section. It would be awful dying one day with this thought stuck in my mind...
I'll also make the assumption that this would work for BLOB as well (if that causes an issue).
Something like this.... you just need to add update clause
CREATE OR REPLACE TRIGGER after_row_insert
AFTER INSERT
ON Z_TEST
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
Begin
insert into Z_TEST2 values(:new.ID, :new.CFIELD);
End;

ORA-04091: table name is mutating

I get ORA-04091 Error while inserting data into table A. Table A records are refferencing other records in the same table 1:N.
Father records have fk_id = null and child records have fk not null.
create or replace trigger TRBI_A
BEFORE INSERT ON A
for each row
BEGIN
IF :new.fk_id IS NOT NULL then
UPDATE A SET actualTS = CURRENT_TIMESTAMP WHERE id = :new.fk_id;
END IF;
END;
ORA-04091: table name is mutating, trigger/function may not see it
The problem could is probably caused by trigger which tried to modify or query a table that is currently being modified by the statement that fired the trigger.
Does anyone know how to modify the trigger to have it correct?
You know what the problem is, so just read your code a little: you update the same table you are putting the trigger on.
I guess in your case you just need to put :NEW.actualTS:=current_timestamp, without using the update statement.

trigger for updating a value

I am a newbie in PLSQL and I would like to create a trigger that checks first if there is a record in a table before making an update.
The code I got so far is:
CREATE OR REPLACE TRIGGER table_bu
BEFORE UPDATE ON employee
FOR EACH ROW
DECLARE
v_employee_id:=employee.employee_ID%TYPE;
BEGIN
SELECT employee_id INTO v_employee_id FROM employee;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001,'data not found');
END;
How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update.
My table estructure is:
employee_id NUMBER
employee_name VARCHAR(20)
employee_salary NUMBER
...
Thanks
You are on a wrong way. The trigger as it is will throw runtime 'Mutating table' error even after fixing syntax error - you missed semicolon after raise_application_error(also it should take 2 arguments, not one). Correct syntax :
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20001, 'data not found'); -- 1st parameter -error code
Update
As far as I understand the updated version of the question, you want to show error if record doesn't exist. The problem with row level trigger approach is that it won't be executed if nothing is found due to condition in WHERE. The simplest way is to check number of rows affected on client side and raise an error there. Or you can write a procedure that checks sql%rowcount after executing desired update, and then throw an exception if it's 0.
If you prefer to do in a hard way, you can create package variable which of type employee.employee_ID%TYPE, before update statement level trigger that resets variable (say set it to null), after update row level trigger that sets this variable to NEW.employee_ID, and after update statement level trigger that throws an exception if the variable is null. Note: this will properly work for individual updates only.
"How I can create a trigger that checks up if a record exists in the table and if it does not exists does not allow the update."
There is really only one practical way to do this - use a referential constraint (foreign key).

sqlite UPDATE trigger firing on INSERT statements also?

I'm working on setting up a simple SQLite database to access via Python. So far I have one basic table, and a couple of triggers - I want to have one trigger update a field column 'date_added' when a new record is added, and another one to update a column 'date_updated' when a record is later updated. Here is my SQLite syntax for the triggers:
CREATE TRIGGER add_contact AFTER INSERT ON contact_info
BEGIN
UPDATE contact_info SET date_added = DATETIME('NOW') WHERE pkid = new.pkid;
END;
CREATE TRIGGER update_contact AFTER UPDATE ON contact_info
BEGIN
UPDATE contact_info SET date_updated = DATETIME('NOW') WHERE pkid = new.pkid;
END;
The 'add_contact' trigger seems to be working fine... it fires when I add a new record via an sql INSERT command, as planned.
The problem seems to be the 'update_contact' trigger... it fires both when I update a record via an sql UPDATE command (as planned) and when I add a new record also:
i.e. when I add a new record I get this in the 'date_added' and 'date_updated' columns:
2010-07-12 05:00:06|2010-07-12 05:00:06
and when I update that record, it changes like so:
2010-07-12 05:00:06|2010-07-12 05:14:26
I guess I'm not getting why the UPDATE trigger fires on INSERT also?
TIA,
Monte
Edited to add: Any hints on how to make it work as intended?
A better way to avoid the original problem is to use a DEFAULT ( DATETIME('NOW') )
clause for the date_added column in the table definition, instead of using an INSERT trigger.
You have an UPDATE in your INSERT trigger. So the INSERT causes an UPDATE. Which you have hooked with a different trigger.

Resources