trigger mutating error - sql

I have emp table in schema1 and emp_fianl in schema2.
emp
empid ename estatus
1 abc incomplete
2 xyz complete
3 ifg incomplete
4 mno incomplete
Emp_final
empid ename estatus
2 xyz complete
I have to create a trigger to insert data in Schema2 emp_final table when the estatus in schema1 emp table changes to complete.
I have written below trigger for the same:
Create or replace trigger tri_emp_final
After update on emp
BEGIN
IF :new.estatus='complete' then
Insert into emp_final
(select :old.empid,:old.ename,:new.estatus from schem1.emp);
END IF;
END;
/
I am getting mutating error message for the above code. When I am trying to update the status in emp table. I am a java developer and do not have much experience in Oracle, SQL. Can anyone please help?

First thing: we you want to use :old and :new, your trigger MUST be FOR EACH ROW. So you need to change : before update on emp for each row.
Second: like Goran Stefanović wrote, you don't make that select to insert, just use the :old values.

Use
Create or replace trigger tri_emp_final
After update on emp for each row
BEGIN
IF :new.estatus='complete' then
Insert into emp_final ( empid , ename , estatus)
Values
(:old.empid,:old.ename,:new.estatus );
END IF;
END;
/
You can find a working demo here

Related

Oracle trigger multiple conditions in when clause

I'm trying to create a trigger that updates a column in a table when other columns are updated. but getting the following error while saving the trigger
ORA-25000: invalid use of bind variable in trigger WHEN clause
My trigger is as follows, I'm not sure what is wrong with the code.
CREATE OR REPLACE TRIGGER Employees_ARIU
BEFORE INSERT OR UPDATE ON Employees
FOR EACH ROW
WHEN ((nvl(:OLD.EMP_SAL,0) != nvl(:NEW.EMP_SAL,0)) OR (nvl(:OLD.SAL_LEVEL,0) != nvl(:NEW.SAL_LEVEL,0)))
BEGIN
:NEW.LAST_UPDATED = SYSDATE
END;
Although IF is an alternative to WHEN, I'd say that it is better to use WHEN clause whenever possible because it is a
SQL condition that must be satisfied for the database to fire the trigger
So, why would you even let the trigger fire and then conclude that oh, OK, I don't want to do anything, after all? Better not running it at all!
Yes, WHEN clause has its restrictions and you can't put anything you want in there, but - your case isn't one of those.
(more info in Documentation, search for "WHEN clause").
So, for a sample table
SQL> create table employees
2 (id number,
3 emp_sal number,
4 sal_level number,
5 last_updated date);
Table created.
trigger would looks like this:
SQL> create or replace trigger employees_ariu
2 before insert or update on employees
3 for each row
4 when ( nvl(old.emp_sal, 0) <> nvl(new.emp_sal, 0)
5 or nvl(old.sal_level, 0) <> nvl(new.sal_level, 0)
6 )
7 begin
8 :new.last_updated := sysdate;
9 end;
10 /
Trigger created.
Testing:
SQL> insert into employees (id, emp_sal, sal_level) values (1, 100, 1);
1 row created.
SQL> select * from employees;
ID EMP_SAL SAL_LEVEL LAST_UPDATED
---------- ---------- ---------- -------------------
1 100 1 12.06.2021 12:14:17
SQL> update employees set sal_level = 2 where id = 1;
1 row updated.
SQL> select * from employees;
ID EMP_SAL SAL_LEVEL LAST_UPDATED
---------- ---------- ---------- -------------------
1 100 2 12.06.2021 12:14:33
SQL>
I think you can try updating your WHEN condition to IF statement along with few other changes -
CREATE OR REPLACE TRIGGER Employees_ARIU
BEFORE INSERT OR UPDATE ON Employees
FOR EACH ROW
BEGIN
IF ((nvl(:OLD.EMP_SAL,0) != nvl(:NEW.EMP_SAL,0)) OR (nvl(:OLD.SAL_LEVEL,0) != nvl(:NEW.SAL_LEVEL,0))) then
:NEW.LAST_UPDATED := SYSDATE;
END IF;
END;
/
Here is the fiddle.

table EMP is mutating, trigger/function may not see it error

I have a table EMP in my apex oracle database that contains an attribute of salary named sal. I have another table EMPSAL that has 3 attributes named averageSal, minSal, maxSal which are to be updated using triggers whenever any DML operation is performed on the EMP table's sal column. Here is the trigger used for upgrading:
create or replace trigger empsal_update_trigger
AFTER update on emp
for each row
declare
avgSal2 emp.sal%type;
minSal2 emp.sal%type;
maxSal2 emp.sal%type;
begin
select avg(sal), min(sal), max(sal) into avgSal2, minSal2, maxSal2 from emp;
delete from empsal;
insert into empsal values(avgSal2, minSal2, maxSal2);
end;
The insert and delete triggers work fine, but the update one given above gives error whenever A record is updated in EMPSAL. I have tried using before keyword instead of after but it's no use.
You don't need a row level trigger for this case, but use a statement level one. Even no need to use local variable definition through use of INSERT INTO ... SELECT... statement.
So, just remove FOR EACH ROW such as
CREATE OR REPLACE TRIGGER empsal_update_trigger AFTER UPDATE ON emp
BEGIN
DELETE empsal;
INSERT INTO empsal
SELECT AVG(sal), MIN(sal), MAX(sal)
FROM emp;
END;
/

there is few mistake in trigger help me to solve that problem

Create trigger that will
not allow update of salary for employee
who are in sales department.
Emp(no,name,dno,salary,designamtion)
Dept(dno,name,location)
Errors: TRIGGER MYTRIGGER1
Line/Col: 6/11 PLS-00049: bad bind variable 'OLD.EMP'
Line/Col: 6/31 PLS-00049: bad bind variable 'OLD.EMP'
DROP TABLE EMP;
DROP TABLE DEPT;
CREATE TABLE Dept
(
dno NUMBER PRIMARY KEY,
name VARCHAR(15),
location VARCHAR(15) );
CREATE TABLE Emp
(
no NUMBER PRIMARY KEY,
name VARCHAR(15),
dno NUMBER,
salary NUMBER,
designamtion VARCHAR(15),
FOREIGN KEY(dno) REFERENCES Dept(dno) );
insert into DEPT values (1,'SALES','GUJARAT');
insert into DEPT values (2,'MARKETING','UP');
insert into DEPT values (3,'MANUFACTURING','MP');
insert into DEPT values (4,'DEALING','VAPI');
insert into DEPT values (5,'SELL','TAPI');
insert into EMP values (1,'AMAN',2,45400,'MANAGER');
insert into EMP values (2,'BHAMAN',5,20000,'GM');
insert into EMP values (3,'CHAMAN',3,34400,'ADVISOR');
insert into EMP values (4,'DAMAN',4,75400,'WORKER');
insert into EMP values (5,'KHAMAN',1,42400,'MANAGER');
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.emp.no=no and :old.emp.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
You don't need to alias the table the trigger is on. This will compile fine:
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.no=no and :old.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
However, this trigger will fail when you update the table. You cannot select from the table itself in the trigger body. This will raise a mutating table error. Since it is an assignment I'll leave the research up to you.
As Koen said, your code will suffer from mutating table error.
Lucky you - you don't have to select from the table you're just updating (the emp table, right?) - you have everything you need in :new.dno. So:
SQL> create or replace trigger mytrigger1
2 before update of salary on emp
3 for each row
4 declare
5 hmmm varchar2(15);
6 begin
7 select d.name
8 into hmmm
9 from dept d
10 where d.dno = :new.dno;
11
12 dbms_output.put_line('Department name = ' || hmmm);
13 end;
14 /
Trigger created.
Testing:
SQL> set serveroutput on
SQL>
SQL> update emp set salary = 1000 where no = 1;
Department name = MARKETING
1 row updated.
SQL> update emp set salary = 1000;
Department name = MARKETING
Department name = SELL
Department name = MANUFACTURING
Department name = DEALING
Department name = SALES
5 rows updated.
SQL>
Your request is a trigger that disallows certain action for employees in Sales. Your trigger would not do so even it compiled. Two problems:
dbms_output does not prevent anything it merely 'prints' a message.
it prevents any update to salary, except when emp.dno or emp.no is also updated.
First (getting up on soapbox). You are enforcing a business rule. This should not be done in a trigger. It should be done in either the business rules engine of your application or in a database constraint or if you want to be super cautions both. Now a trigger will do the enforcement but business rule violations should glaringly obvious and easy to find, in a trigger becomes a side effect of an action, hidden away and difficult to find. (OK enough of that getting off soapbox).
The prevention method your looking for is raise_application_error. And you want a little code as possible in a trigger. In this case you have everything needed from the EMP table through the pesudo rows :old and :new so there is no reason to join your tables. However you do need to look at the specific department. It is too bad Oracle constrains the Exists predicate to introduction of a sub-select, this would be a perfect place for the structure:
If EXISTS (selects ...) then ...
But that is not available. The following uses a technique of reversing the requirement. That is it selects what is NOT wanted then if found it raise the exception, and if not found it suppress the Oracle raised error. So:
create or replace trigger mytrigger1
before update of salary on emp
for each row
declare
hmmm varchar2(01);
begin
select null
into hmmm
from dept d
where d.dno = :old.dno
and d.name = 'SALES';
raise_application_error(-20199, 'Can not update salary for Employee in Sales.');
exception
when no_data_found then null;
end mytrigger1;
This is sometimes referred to as "Programming the Exception" and is not a generally recommended procedure but at times it is useful. See fiddle here.
There is an outstanding question not addressed here. Can an employee in Sales have a salary change if they are also transferring departments. I.e. should the following succeed or fail?
update emp e
set dno = 2
, salary = salary + 5000
where e.no = 5;

How to create the views using trigger?

Ca you please suggest if I can create the db view using triggers in Oracle?
E.g. I have a trigger trig_cust and I want to create a view:
Create or replace view vw_cust as select * from trig_cust;
P.S. need to use this view in loop
Triggers are an even of action. You cannot create a view out of trigger.
Triggers are used to perform an action insert/ update / delete based on particular event where as views are used to select set of columns with the combination of multiple tables.
View is used to display a combined set of required columns from various tables in order to reduce the querying effort. View is majorly used for reporting purpose. You can have a trigger on the view, but not view out of trigger.
suggest if I can create the db view using triggers in Oracle?
Can you? Yes, you can. Should you? No, you shouldn't.
Anyway, just for amusement, here you go: table and its trigger which is supposed to create a view once a new row is inserted into a table. Pay attention to pragma; without it, it wouldn't work as you can't commit in a trigger. True, there's no explicit commit there, but create view is a DDL and it implicitly commits.
SQL> create table test (empno number, ename varchar2(20));
Table created.
SQL> create or replace trigger trg_ai_test
2 after insert on test
3 for each row
4 declare
5 pragma autonomous_transaction;
6 begin
7 execute immediate 'create or replace view v_test_' || to_char(:new.empno) ||
8 ' as select * from test where empno = ' || :new.empno;
9 end;
10 /
Trigger created.
Testing:
SQL> insert into test (empno, ename) values (1, 'Little');
1 row created.
SQL> insert into test (empno, ename) values (2, 'Foot');
1 row created.
SQL> select * from v_test_1;
EMPNO ENAME
---------- --------------------
1 Little
SQL> select * from v_test_2;
EMPNO ENAME
---------- --------------------
2 Foot
SQL>

Using oracle triggers to update a second table where a condition has been

I'm new to pl/sql and grappling with triggers. I am required to use a trigger for this code. I have 2 tables, job (job_id, job_name, job_price) and job_history (job_id, oldprice, datechanged). I'm trying to create a trigger that adds the old job details to the job_history table when the job_price field in the job table is updated either if no row already exist or if the new job price for that job id is more than any previously stored prices for that job id in the job_history table. The job id field in the job table cannot have duplicates but the job id field in the job_history table can have duplicates. Further, if the condition is not met, that is, the new job price is less than all previously stored prices for that job id, then the error should be trapped.
I've tried this code:
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
WHEN (new.jbsprice)<min(old.jbsprice);
BEGIN
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
IF :new.price is<>min(oldprice) THEN
RAISE_APPLICATION_ERROR('Condition not met.');
ENDIF;
END;
/
This resulted in an error at line 4
ORA-00920: invalid relational operator.
I've checked the oracle online documentation. It's confusing. Do I need to use a cursor and loop inside the trigger? The less than operator looks okay and the min(function) looks okay. I cannot see where I'm going wrong. Please help.
at a first glance, I would suggest the following:
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
DECLARE
hist_exists number;
BEGIN
hist_exists := 0;
begin
-- select 1 if there is an entry in job_history of that jbsid
-- and an oldprice exists which is more than new jbsprice
select distinct 1
into hist_exists
from job_history
where jbsid = :old.jbsid
and oldprice > :new.jbsprice;
exception when no_data_found then hist_exists := 0;
end;
IF hist_exists = 0 then
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
END IF;
END;
/
Ignoring :
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
WHEN (:new.jbsprice)<min(:old.jbsprice);
BEGIN
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
IF :new.price is<>min(oldprice) THEN
RAISE_APPLICATION_ERROR('Condition not met.');
ENDIF;
END;