How to avoid Getting ORACLE Mutating trigger error - oracle

I created the trigger to update the oracle data base table after insert.
CREATE OR REPLACE TRIGGER Update_ACU
AFTER INSERT ON TBL_ACU
FOR EACH ROW
BEGIN
UPDATE TBL_ACU
SET CURRENCY = 'XXX'
WHERE ACCOUNT like '%1568';
END ;
I inserted record as
insert into TBL_ACU values('23','USD','1231568');
I am getting table ORACLE Mutating trigger error.
Please help me how to resolve this.

It would be better to use BEFORE INSERT trigger to do this.
Try like this,
CREATE OR REPLACE
TRIGGER update_acu
BEFORE INSERT ON tbl_acu
FOR EACH ROW
WHEN (NEW.ACCOUNT LIKE '%1568')
BEGIN
:NEW.currency := 'XXX';
END ;

Well, you cannot modify the table from the trigger if the trigger is called upon modification of that table. There are various solutions to this problem including an AFTER STATEMENT trigger and caching modifications in some collection defined in PL/SQL PACKAGE, howewer in your situation I'd rather change the body of your trigger to this:
BEGIN
IF :NEW.ACCOUNT LIKE '%1568' THEN
:NEW.CURRENCY := 'XXX';
END IF;
END;
You can use the :NEW and :OLD variables inside the trigger, which identify the new and old values of the record accordingly. Modifying values of the :NEW record will cause changes in data actually inserted to the database.

Related

Mutating Trigger Error with Trigger in Oracle PL/SQL [duplicate]

I get an error (ORA-04091: table DBPROJEKT_AKTIENDEPOT.AKTIE is mutating, trigger/function may not see it) when executing my trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
AFTER
INSERT OR UPDATE OF TAGESKURS
OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
bfr number;
Begin
bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
UPDATE AKTIE
SET BILANZ = TAGESKURS - WERT_BEIM_EINKAUF;
IF bfr < -50
THEN
DBMS_OUTPUT.PUT_LINE('ACHTUNG: The value (Nr: '||:new.AKTIEN_NR||') is very low!');
END IF;
END;
I want to check the value "BILANZ" after calculating it, wether it is under -50.
Do you have any idea why this error is thrown?
Thanks for any help!
There are several issues here:
Oracle does not allow you to perform a SELECT/INSERT/UPDATE/DELETE against a table within a row trigger defined on that table or any code called from such a trigger, which is why an error occurred at run time. There are ways to work around this - for example, you can read my answers to this question and this question - but in general you will have to avoid accessing the table on which a row trigger is defined from within the trigger.
The calculation which is being performed in this trigger is what is referred to as business logic and should not be performed in a trigger. Putting logic such as this in a trigger, no matter how convenient it may seem to be, will end up being very confusing to anyone who has to maintain this code because the value of BILANZ is changed where someone who is reading the application code's INSERT or UPDATE statement can't see it. This calculation should be performed in the INSERT or UPDATE statement, not in a trigger. It considered good practice to define a procedure to perform INSERT/UPDATE/DELETE operations on a table so that all such calculations can be captured in one place, instead of being spread out throughout your code base.
Within a BEFORE ROW trigger you can modify the values of the fields in the :NEW row variable to change values before they're written to the database. There are times that this is acceptable, such as when setting columns which track when and by whom a row was last changed, but in general it's considered a bad idea.
Best of luck.
You are modifying the table with the trigger. Use a before update trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
BEFORE INSERT OR UPDATE OF TAGESKURS OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
v_bfr number;
BEGIN
v_bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
:new.BILANZ := v_bfr;
IF v_bfr < -50 THEN
Raise_Application_Error(-20456,'ACHTUNG: The value (Nr: '|| :new.AKTIEN_NR || ') is very low!');
END IF;
END;

Before-insert trigger gets 'too many rows' error

I have a trigger:
create or replace trigger trig
before insert on sistem
for each row
declare
v_orta number;
begin
SELECT v_orta INTO :new.orta_qiymet
FROM sistem;
v_orta:=(:new.riyaziyyat+:new.fizika)/2;
insert into sistem(orta_qiymet)
values(v_orta);
end trig;
When I insert a row:
insert into sistem(riyaziyyat,fizika) values(4,4)
I get an error:
Why am I getting that error?
This is fundamentally not understanding how triggers work. You can't generally select from the table the trigger is against, and a before-insert trigger shouldn't not insert into the same table again - as that would just cause the trigger to fire again, infinitely (until Oracle notices and stops it). You aren't even currently using the v_orta value you're attempting to query.
I suspect you think the trigger is instead of your original insert perhaps, and really you want to set the orta_qiymet value in the newly-inserted row automatically based on the other two columns you have supplied. To do that you don't (and can't) select those values; instead you refer to the :NEW pseudorecord as you are already doing, and then set the third column value in that same pseudorow:
create or replace trigger trig
before insert on sistem
for each row
begin
:new.orta_qiymet := (:new.riyaziyyat + :new.fizika)/2;
end trig;
/
There is a lot of information in the documentation; this is similar to one of the examples.

How to update a table using an AFTER INSERT trigger in that same table?

I have 2 triggers on a table A one is BEFORE INSERT and one AFTER INSERT :
CREATE OR REPLACE TRIGGER DEMO_TRG
AFTER INSERT
ON A
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin
UPDATE A
SET STATUS = 'DONE'
WHERE id_id =:new.p_id;
commit;
END;
/
Why the update command in the after insert doesn't work (the status is not set to DONE)? Did I missed something?
Most likely the issue is due to the AUTONOMOUS_TRANSACTION - when you do that, you're effectively switching to a different transaction, which won't know about your uncommitted transaction (ie. your insert), so there is nothing for it to update.
What you should do is amend the column (:new.status := 'DONE') in your before trigger. Or, even better, avoid triggers and have the logic in a stored procedure and don't allow anyone to directly insert into the table, although I appreciate this may be a big switch if you have lots of applications inserting directly into tables etc

Mutating error on after insert trigger

The below code is giving a mutating error.
Can any1 pls help in solving this.
CREATE OR REPLACE TRIGGER aso_quote_cuhk_trigger
BEFORE INSERT
ON aso.aso_quote_headers_all
FOR EACH ROW
BEGIN
UPDATE aso.aso_quote_headers_all
SET quote_expiration_date=sysdate+90
where quote_header_id=:new.quote_header_id;
END;
/
In oracle there are two levels of triggers: row level and table level.
Row level triggers are executed for each row. Table level triggers executed per statement, even if a statement changed more then one row.
In a row level trigger, you cannot select/update the table itself that has the trigger: you will get a mutating error.
In this case, there is no need for an UPDATE statement. Just try this:
CREATE OR REPLACE TRIGGER aso_quote_cuhk_trigger
BEFORE INSERT
ON aso.aso_quote_headers_all
FOR EACH ROW
BEGIN
:new.quote_expiration_date=sysdate+90;
END;
/
EDIT Rajesh mentioned it is possible, that before inserting a new row, OP wants to update all other records in the aso_quote_headers_all table.
Well, this is feasible, but it's a little tricky. To do this properly, you will need
A pl/sql package and a variable in the package header that is modified by the triggers. This variable could be a list holding the IDs of newly inserted records. Row level after insert trigger would add a new ID to the list. The content of this package variable will be different for each different session, so let's call this variable session_variable.
Row level after insert trigger, that would add new ID to the session_variable.
Table level after insert trigger that would get IDs from the session_variable, process the ID and then remove it from the session_variable. This trigger could execute necessary selects/updates on the aso_quote_headers_all. After a newly inserted ID is processed, this trigger should make sure it gets removed from the session_variable.
I realise you must have resolved your issue by now. However I am adding this answer below to help anyone else facing similar problem as you and I faced.
I recently encountered mutating table (ORA-04091: table XXXX is mutating, trigger/function may not see it) issue and after searching around realised the Compound Triggers feature available in 11g. If you're on 11g following compound trigger would have solved your issue.
CREATE OR REPLACE TRIGGER aso_quote_cuhk_trigger
FOR INSERT ON aso.aso_quote_headers_all
COMPOUND TRIGGER
row_id rowid;
AFTER EACH ROW IS
BEGIN
row_id := :new.rowid;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
UPDATE aso.aso_quote_headers_all
SET quote_expiration_date = sysdate+90
WHERE rowid = row_id;
END AFTER STATEMENT;
END aso_quote_cuhk_trigger;
/
A word about how it works. This compound trigger fires 2 events :
First is AFTER EACH ROW where we capture the rowid of newly inserted row
Next is AFTER STATEMENT where we update the table using rowid (captured during first event) in the WHERE clause.
A useful link if you want to read more about Compound Triggers.

Can I copy :OLD and :NEW pseudo-records in/to an Oracle stored procedure?

I have an AFTER INSERT OR UPDATE OR DELETE trigger that I'm writing to store every record revision that occurs in a certain table, by copying the INSERT and UPDATE :NEW values into a mirror table, and for DELETE the :OLD values.
I could un-clutter my code considerably by conditionally passing either the :NEW or :OLD record into a procedure which would then do the insert into my history table. Unfortunately I cannot seem to find a way to pass the entire :OLD or :NEW record.
Am I missing something or is there no way to avoid enumerating every :NEW and :OLD column as I invoke my insert procedure?
I want to do the following:
DECLARE
PROCEDURE LOCAL_INSERT(historyRecord in ACCT.ACCOUNTS%ROWTYPE) IS
BEGIN
INSERT INTO ACCT.ACCOUNTS_HISTORY (ID, NAME, DESCRIPTION, DATE) VALUES (historyRecord.ID, historyRecord.NAME, historyRecord.DESCRIPTION, SYSDATE);
END;
BEGIN
IF INSERTING OR UPDATING THEN
LOCAL_INSERT(:NEW);
ELSE --DELETING
LOCAL_INSERT(:OLD);
END IF;
END;
But I'm stuck doing this:
DECLARE
PROCEDURE LOCAL_INSERT(id in ACCT.ACCOUNTS.ID%TYPE,
name in ACCT.ACCOUNTS.NAME%TYPE,
description in ACCT.ACCOUNTS.DESCRIPTION%TYPE) IS
BEGIN
INSERT INTO ACCT.ACCOUNTS_HISTORY (ID, NAME, DESCRIPTION, DATE) VALUES (id, name, description, SYSDATE);
END;
BEGIN
IF INSERTING OR UPDATING THEN
LOCAL_INSERT(:NEW.ID, :NEW.NAME, :NEW.DESCRIPTION);
ELSE --DELETING
LOCAL_INSERT(:OLD.ID, :OLD.NAME, :OLD.DESCRIPTION);
END IF;
END;
Okay, so it doesn't look like a big difference, but this is just an example with 3 columns rather than dozens.
It isn't. You have to do it yourself through enumeration.
The reasons it can't/doesn't work automatically include:
the :old and :new are default conventions; you can name the :old and :new references to be whatever you want through the REFERENCING clause of the CREATE TRIGGER statement.
you'd have to have a public declaration of a type (through CREATE TYPE or through a package declaration) to be able to use it as an argument to another piece of code.
trigger code is interpreted code, not compiled code.
I don't think it's possible like that. Documentation doesn't mention anything like that.
This would certainly cost performance, but you could try to define your trigger AFTER INSERT and another one BEFORE UPDATE OR DELETE, and in the trigger do something like:
SELECT *
INTO rowtype_variable
FROM accounts
WHERE accounts.id = :NEW.id; -- :OLD.id for UPDATE and DELETE
and then call your procedure with that rowtype_variable.
Use SQL to generate the SQL;
select ' row_field.'||COLUMN_NAME||' := :new.'||COLUMN_NAME||';' from
ALL_TAB_COLUMNS cols
where
cols.TABLE_NAME = 'yourTableName'
order by cols.column_name.
Then copy and paste output.
If you use AFTER trigger you can use rowid as parameter to call procedure
insert into t_hist
select * from t where rowid = r;
If you use BEFORE trigger you will get ORA-04091 mutating table, BUT you solution can be (http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm):
Don't use triggers - The best way to avoid the mutating table error is not to use triggers. While the object-oriented Oracle provides "methods" that are associated with tables, most savvy PL/SQL developers avoid triggers unless absolutely necessary.
Use an "after" or "instead of" trigger - If you must use a trigger, it's best to avoid the mutating table error by using an "after" trigger, to avoid the currency issues associated with a mutating table. For example, using a trigger ":after update on xxx", the original update has completed and the table will not be mutating.
Re-work the trigger syntax - Dr. Hall has some great notes on mutating table errors, and offers other ways to avoid mutating tables with a combination of row-level and statement-level triggers.
Use autonomous transactions - You can avoid the mutating table error by marking your trigger as an autonomous transaction, making it independent from the table that calls the procedure.

Resources