Mutating table error while executing row level after update trigger - oracle

I have a use case where I need to place a trigger on a table(TABLE1) whenever its column COL1 is set to 1. When this trigger executes, it should update a column of another table(TABLE2). This column gets it value from a complex query which takes input from TABLE1. Here is my trigger code:(Compiled Successfully)
CREATE OR REPLACE TRIGGER MY_TRIGGER
AFTER UPDATE ON TABLE1
FOR EACH ROW
WHEN (NEW.COL1='1')
DECLARE
Var1 Number;
Var2 Number;
BEGIN
SELECT COL3,COL2 INTO Var1,Var2 FROM TABLE1;
UPDATE TABLE2 T2
SET T2.COL1 = (Complex query joining multiple tables which takes Var1 as input and gives one column value as output- This value has to be set to T2.COL1)
WHERE T2.COL2 = Var2;
END;
In my application, TABLE1.COL1 keeps changing at run time due to different activities and I have to capture it and update my TABLE2.COL1. In this trigger there is no update being performed on TABLE1 on which trigger is defined. Still I am getting below mutating error whenever an update happens on TABLE1 at runtime. Also when my trigger is enabled the application level activities are also not letting the TABLE1 to update and giving this error.
Please help me to resolve this issue.
Getting SQL Error: ORA-04091: table DBUSERNAME.TABLE1 is mutating, trigger/function may not see it
ORA-04088: error during execution of trigger 'DBUSERNAME.MY_TRIGGER'
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it

Don't select columns from the table you're modifying, it is mutating (as you already know). Use :new!
Here's an example based on Scott's EMP table.
This is a table I'll use as a target of UPDATE statement in the trigger:
SQL> create table test as select empno, sal from emp;
Table created.
Trigger: note lines #6 and #7:
SQL> create or replace trigger my_trigger
2 after update on emp
3 for each row
4 begin
5 update test t2 set
6 t2.sal = :new.sal
7 where t2.empno = :new.empno;
8 end;
9 /
Trigger created.
Testing:
SQL> select empno, sal from emp where ename = 'KING';
EMPNO SAL
---------- ----------
7839 5000
SQL> update emp set sal = 5001 where ename = 'KING';
1 row updated.
SQL> select * from test where empno = 7839;
EMPNO SAL
---------- ----------
7839 5001
SQL>

Related

How to execute trigger on delete or update on specific column happen in oracle?

I want to write the trigger that copies the old data into new table whenever the delete or update is happen.
So I have two tables tableA and tableB
tableA has following attributes: rollno, name, and status
tableB has following attributes: rollno, name
So, first whenever the delete operation is made on tableA I want to copy the old values to the tableB or second Whenever the value of the status attribute on tableA changes to specific value say 'C' then also I have to copy the old values.
I had written trigger that will copy the old values into tableB from tableA whenever delete or update is performed but the trigger is also executing for any update on the tableA.
Here is my trigger code
create or replace trigger my_trigger
before delete or update
on tableA
for each row
begin
insert into tableB values(:OLD.rollno,:OLD.name);
end;
So how to execute trigger if the status attribute is updated?
I can check by using if statement in trigger that if the :NEW value is 'c' then execute the trigger but I also want to execute the trigger for delete statement also how can I do this?
I think I can use two triggers one for delete and one for update and inside update trigger i can check my condition but can I do this in one trigger only in oracle?
Yes, IF can help.
Here's an example based on Scott's schema; have a look.
This is a log table:
SQL> create table deptb as select * From dept where 1 = 2;
Table created.
Trigger:
SQL> create or replace trigger trg_bdu_dept
2 before delete or update on dept
3 for each row
4 begin
5 if deleting then
6 insert into deptb values (:old.deptno, :old.dname, :old.loc);
7 elsif updating and :old.loc = 'NEW YORK' then
8 insert into deptb values (:old.deptno, :old.dname, :old.loc);
9 end if;
10 end;
11 /
Trigger created.
SQL>
Testing:
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------------- --------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> delete from dept where deptno = 40;
1 row deleted.
SQL> update dept set loc = 'NY' where loc = 'NEW YORK';
1 row updated.
SQL> update dept set loc = 'dallas' where loc = 'DALLAS';
1 row updated.
SQL> select * from deptb;
DEPTNO DNAME LOC
---------- -------------------- --------------------
40 OPERATIONS BOSTON
10 ACCOUNTING NEW YORK
SQL> select * from dept;
DEPTNO DNAME LOC
---------- -------------------- --------------------
10 ACCOUNTING NY
20 RESEARCH dallas
30 SALES CHICAGO
SQL>
You can do this as following:
create or replace trigger my_trigger
before delete or update of status
on tableA
for each row
WHEN (NEW.status= 'C' OR NEW.status IS NULL)
begin
insert into tableB values(:OLD.rollno,:OLD.name);
end;
Only look for updates of column status with update of status. And then only execute the trigger when the new column value matches some condition WHEN (NEW.status= 'C' OR NEW.status IS NULL) checking for your value or null (in the delete case).

ORA-04091: table is mutating when using cursor in trigger to insert in other table

create table dept(dno number(3) primary key)
create table emp(eno number(3) primary key,dno number(3) references dept)
create table emp_cnt(dno number(3),cnt number(3),foreign key(dno) references dept)
insert all
into dept values(101)
into dept values(102)
into dept values(103)
into dept values(104)
into dept values(105)
select * from dual
create or replace trigger count_emp after insert or update or delete on emp for each row
declare
cursor c1 is select dno,count(eno) cnt from emp group by dno;
begin
for row in c1
loop
insert into emp_cnt(dno,cnt) values(row.dno,row.cnt);
end loop;
end;
insert into emp values(1,101)
when I try to insert the data in the 'emp' table like the above statement, it shows me an error telling that my 'emp' table is mutating. Below i have shown the exact error that it shows
ORA-04091: table SYSTEM.EMP is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM.COUNT_EMP", line 2
ORA-06512: at "SYSTEM.COUNT_EMP", line 4
ORA-04088: error during execution of trigger 'SYSTEM.COUNT_EMP'
1. insert into emp values(1,101)
with the last insert I'm inserting in the 'emp' table, which will invoke the trigger 'emp_count', in this trigger I'm using cursor to count the number of employees in each department and then I'm insert that data of cursor in the 'emp_cnt' table
As mentioned in the comments, you cannot query or modify the table which is the Trigger owner, which if you do causes the error.
A Trigger is not meant for such requirements. Use a View instead.
create or replace view emp_cnt
AS
select dno,count(eno) cnt from emp
group by dno;
insert into emp values(1,101);
insert into emp values(2,101);
insert into emp values(3,102);
select * from emp_cnt;
DNO CNT
102 1
101 2
Demo

Raise_application_error() does stop execution ? (Stop inserting/delete/update in table)

Raise_application_error() With "Before Delete" Trigger can prevent and stop the delete from the table ?
Yes, raise_application_error can prevent and stop delete. Consider the following example :
SQL> desc emp
Name Type Nullable Default Comments
-------- ------------ -------- ------- --------
EMPNO NUMBER(4)
ENAME VARCHAR2(10) Y
JOB VARCHAR2(9) Y
MGR NUMBER(4) Y
HIREDATE DATE Y
SAL NUMBER(7,2) Y
COMM NUMBER(7,2) Y
DEPTNO NUMBER(2) Y
SQL> create or replace trigger trg_del_emp
2 before delete on emp
3 for each row
4 declare
5 begin
6 if ( :old.deptno = 10 ) then
7 raise_application_error(-20222,'Records with Deptno=10 can not be deleted!');
8 end if;
9 end;
10 /
Trigger created
SQL> insert all
2 into emp values(7782,'CLARK','MANAGER',7839, date'1981-06-09',2450.00,null,10)
3 into emp values(7788,'SCOTT','ANALYST',7566, date'1987-04-19',3000.00,null,20)
4 select * from dual;
2 rows inserted
SQL> delete emp where empno = 7782;
delete emp where empno = 7782
ORA-20222: Records with Deptno=10 can not be deleted
ORA-06512: at "HR.TRG_DEL_EMP", line 4
ORA-04088: error during execution of trigger 'HR.TRG_DEL_EMP'
SQL> delete emp where empno = 7788;
1 row deleted
SQL> rollback;
Rollback complete
Yes, it will "stop execution". From Using Triggers:
Error Conditions and Exceptions in the Trigger Body
If a predefined or user-defined error condition (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.

Count of table before and after insert inside a trigger

Is it possible to check the count of a table before any changes happen and the count after the insert and match them inside the same trigger?
for ex: old.count and new.count (before and after insert) ?
old.count and new.count (before and after insert)
Nothing stops you from using SELECT COUNT(*) in a before insert trigger. Of course, you won't do it in a after insert trigger, since a select count(*) on the same table on which an after trigger is defined would throw mutating table error. One way is autonomous transaction. But, in your case, it isn't that complex.
You could define a BEFORE INSERT TRIGGER and take the table count. The after insert count could be taken manually after the actual insert is done.
For example, I have a table t1 with one row. I have defined a before insert trigger on it, which would give me the table count before the insert happens.
SQL> DROP TABLE t1 PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE t1 (A NUMBER);
Table created.
SQL>
SQL> INSERT INTO t1 VALUES (1);
1 row created.
SQL>
SQL> SELECT * FROM t1;
A
----------
1
SQL>
SQL> CREATE OR REPLACE TRIGGER trg
2 BEFORE INSERT
3 ON t1
4 FOR EACH ROW
5
6 DECLARE
7 val number;
8 BEGIN
9 SELECT COUNT(*)
10 INTO val
11 FROM t1;
12
13 DBMS_OUTPUT.PUT_LINE('TABLE COUNT BEFORE INSERT = '||val);
14
15 END;
16 /
Trigger created.
SQL>
SQL> set serveroutput on
SQL> INSERT INTO t1 VALUES (1);
TABLE COUNT BEFORE INSERT = 1
1 row created.
SQL>
SQL> SELECT COUNT(*) FROM t1;
COUNT(*)
----------
2
SQL>
So, you see TABLE COUNT BEFORE INSERT = 1 and then after insert the count is 2.

Triggers in Oracle and how to keep the records after a rollback

I need to create a trigger that writes changes in a shadow table. I know how to create the trigger but my challenge is that I need the records in the new table to exist even after a rollback.
This is an example of how the output will look like
INSERT INTO department VALUES (95, 'PURCHASING', 'CHICAGO');<br>
ROLLBACK;
1 rows inserted.
rollback complete.
SELECT * FROM department_log;
DEPARTMENT_ID DEPARTMENT_NAME ADDRESS OPERATION_TIME
---------------------- -------------------- -------------------- ------------------
90 HR CHICAGO 03-NOV-11
95 PURCHASING CHICAGO 03-NOV-11
SELECT * from department WHERE department_id >= 90;
DEPARTMENT_ID DEPARTMENT_NAME ADDRESS
---------------------- -------------------- --------------------
90 HR CHICAGO
You'll need to use autonomous transactions.
SQL> create table t (col1 number);
Table created.
SQL> create table t_shadow( col1 number, dt date );
Table created.
SQL> create trigger trg_t
2 before insert on t
3 for each row
4 declare
5 pragma autonomous_transaction;
6 begin
7 insert into t_shadow( col1, dt )
8 values( :new.col1, sysdate );
9 commit;
10 end;
11 /
Trigger created.
SQL> insert into t values( 1 );
1 row created.
SQL> rollback;
Rollback complete.
SQL> select * from t;
no rows selected
SQL> select * from t_shadow;
COL1 DT
---------- ---------
1 09-NOV-11
Note that if you find yourself using autonomous transactions for anything other than persistent logging, you are almost certainly doing something wrong. Autonomous transactions are a very dangerous and very frequently misused feature.
You would need to declare the trigger as an Autonomous Transaction
PRAGMA AUTONOMOUS_TRANSACTION;
This decouples the trigger code from the main transaction, so even if the main insertion into the table (which fired the trigger) rollsback, the trigger is executed in a different transactional context and can commit / rollback independently.

Resources