How can I solve the mutating table error here? - oracle

set serveroutput on;
CREATE OR REPLACE TRIGGER hw3
BEFORE DELETE on ENROLLS
for EACH ROW
ENABLE
DECLARE
v_sid number;
v_term varchar2(20);
v_sectno number;
v_COMPNAME varchar2(20);
v_points number;
BEGIN
select :old.SID,:old.TERM,:old.SECTNO into v_sid,v_term,v_sectno from enrolls;
select COMPNAME,points into v_compname,v_points from scores
where scores.sid=v_sid and scores.term=v_term and scores.sectno=v_sectno;
INSERT into DELETED_SCORES (SID,TERM,SECTNO,compname,points)
values (v_sid,v_term,v_sectno,v_compname,v_points);
DELETE FROM SCORES
WHERE SID=V_SID AND TERM=V_TERM AND SECTNO=V_SECTNO;
END;
/
There are two table, which is enrolls and scores. And SCORES table has a combined foreign keys including SID,TERM,AND SECTNO referring to table ENROLLS.
The trigger is now successfully compiled, but there is a problem shown as below:
Error starting at line : 24 in command -
DELETE FROM enrolls
WHERE SID=1111 and term='F12' and sectno=1031
Error report -
SQL Error: ORA-04091: table C16_HE_JIEL.ENROLLS is mutating, trigger/function may not see it
ORA-06512: at "C16_HE_JIEL.HW3", line 8
ORA-04088: error during execution of trigger 'C16_HE_JIEL.HW3'
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.

never use before trigger for DML. In some situations before trigger can be fired more than once for the same row. It might happen in situations when your session is blocked by other session updating/deleting the same row.
Use compound trigger and apply DML changes in after trigger session.
Then you can be sure, that data you see are really correct.
The mutating table error means that Oracle can not quarantee that what you're doing is deterministic.

This select:
select :old.SID,:old.TERM,:old.SECTNO into v_sid,v_term,v_sectno from enrolls;
Is invalid for two reasons:
you can't select from the table where the trigger fired on
you are selecting all rows from that table, not just one.
The select isn't needed anyway, you can simply use the values in the OLD record directly (and remove the invalid select ... from enrolls)
select COMPNAME,points
into v_compname,v_points
from scores
where scores.sid = :old.SID
and scores.term = :old.TERM
and scores.sectno = :old.SECTNO;
The above statement requires that the combination of (sid, term, secno) is unique in the table scores. If that is not the case and you need to insert multiple rows with into deleted_scores you need to use an INSERT based on a select.
Which then removes the need for variables completely. So the whole trigger can be simplified to:
CREATE OR REPLACE TRIGGER hw3
BEFORE DELETE on ENROLLS
for EACH ROW
BEGIN
INSERT into DELETED_SCORES (SID,TERM,SECTNO,compname,points)
select sid, term, sectno, compname, points
from scores
where scores.sid = :old.SID
and scores.term = :old.TERM
and scores.sectno = :old.SECTNO;
DELETE FROM SCORES
WHERE SID = :OLD.sid
AND TERM = :OLD.term
AND SECTNO = :OLD.sectno;
END;
/
More details about the "table is mutating" restriction can be found in the manual:
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS759

Related

Trigger of two tables in an audit

Good morning, I am trying to audit two tables, and I have investigated that it cannot be done, therefore I explain what I wanted to do:
I have two tables (Participante, Actividad) which are joined by a third table (Part_Actividad)
I want to audit the Participants table, but in the same way I need the Id of the activity to know in which activity the data of a participant is changed.
Ideas I had
Create a trigger like the following
CREATE OR REPLACE TRIGGER Tri_Auditoria
AFTER INSERT ON Participante
FOR EACH ROW
DECLARE
v_Participante_ID Participante.Participante_ID%TYPE;
v_Actividad_ID Part_Actividad.Actividad_ID%TYPE;
BEGIN
SELECT participante_ID INTO v_Participante_ID
FROM Participante;
SELECT Actividad_ID INTO v_Actividad_ID
FROM Part_Actividad
WHERE PartAct_ID = v_Participante_ID;
INSERT INTO Auditoria(Auditoria_ID, Actividad_ID, Participante_ID, TipPart_ID_Ant, Part_P_Nombre_Ant, Part_P_Apell_Ant, Part_Cedula_Ant, Part_Genero_ant, Part_FNaci_Ant, Aud_Operacion, Usuario_Modificador, Fecha_Modificacion)
VALUES (sec_Auditoria.nextval, v_Actividad_ID, :new.Participante_ID,:new.TipPart_ID,:new.Part_P_Nom,:new.Part_P_Apell,:new.Part_Cedula,:new.Part_Genero,:new.Part_FNaci,'I',USER,sysdate);
END Tri_Auditoria;
/
Result:
When I insert data into the participante table, it is not inserted and it sends a trigger error.
Mutating table error, isn't it? That's because you're selecting from the same table which caused trigger to fire, and that's not allowed.
Anyway, you shouldn't do that because you have that value in disposal - just reference it using the :new pseudorecord, such as
CREATE OR REPLACE TRIGGER Tri_Auditoria
AFTER INSERT ON Participante
FOR EACH ROW
DECLARE
v_Actividad_ID Part_Actividad.Actividad_ID%TYPE;
BEGIN
SELECT Actividad_ID
INTO v_Actividad_ID
FROM Part_Actividad
WHERE PartAct_ID = :new.participante_ID; --> this
INSERT INTO Auditoria
(Auditoria_ID, Actividad_ID, Participante_ID,
TipPart_ID_Ant, Part_P_Nombre_Ant, Part_P_Apell_Ant,
Part_Cedula_Ant, Part_Genero_ant, Part_FNaci_Ant,
Aud_Operacion, Usuario_Modificador, Fecha_Modificacion)
VALUES (sec_Auditoria.nextval, v_Actividad_ID, :new.Participante_ID,
:new.TipPart_ID,:new.Part_P_Nom,:new.Part_P_Apell,
:new.Part_Cedula,:new.Part_Genero,:new.Part_FNaci,
'I',USER,sysdate);
END Tri_Auditoria;
/

Trying to delete a row based upon condition defined in my trigger (SQL)

I am trying to create a row level trigger to delete a row if a value in the row is being made NULL. My business parameters state that if a value is being made null, then the row must be deleted. Also, I cannot use a global variable.
BEGIN
IF :NEW.EXHIBIT_ID IS NULL THEN
DELETE SHOWING
WHERE EXHIBIT_ID = :OLD.EXHIBIT_ID;
END IF;
I get the following errors:
ORA-04091: table ISA722.SHOWING is mutating, trigger/function may not see it
ORA-06512: at "ISA722.TRG_EXPAINT", line 7
ORA-04088: error during execution of trigger 'ISA722.TRG_EXPAINT'
When executing this query:
UPDATE SHOWING
SET EXHIBIT_ID = NULL
WHERE PAINT_ID = 5104
As already indicated this is a terrible idea/design. Triggers are very poor methods for enforcing business rules. These should be enforced in the application or better (IMO) by a stored procedure called by the application. In this case not only is it a bad idea, but it cannot be implemented as desired. Within a trigger Oracle does not permit accessing the table the trigger fired was fired on. That is what mutating indicates. Think of trying to debug this or resolve a problem a week later. Nevertheless this non-sense can be accomplished by creating view and processing against it instead of the table.
-- setup
create table showing (exhibit_id integer, exhibit_name varchar2(50));
create view show as select * from showing;
-- trigger on VIEW
create or replace trigger show_iiur
instead of insert or update on show
for each row
begin
merge into showing
using (select :new.exhibit_id new_eid
, :old.exhibit_id old_eid
, :new.exhibit_name new_ename
from dual
) on (exhibit_id = old_eid)
when matched then
update set exhibit_name = new_ename
delete where new_eid is null
when not matched then
insert (exhibit_id, exhibit_name)
values (:new.exhibit_id, :new.exhibit_name);
end ;
-- test data
insert into show(exhibit_id, exhibit_name)
select 1,'abc' from dual union all
select 2,'def' from dual union all
select 3,'ghi' from dual;
-- 3 rows inserted
select * from show;
--- test
update show
set exhibit_name = 'XyZ'
where exhibit_id = 3;
-- 1 row updated
-- Now for the requested action. Turn the UPDATE into a DELETE
update show
set exhibit_id = null
where exhibit_name = 'def';
-- 1 row updated
select * from show;
-- table and view are the same (expect o rows)
select * from show MINUS select * from showing
UNION ALL
select * from showing MINUS select * from show;
Again this is a bad option yet you can do. But just because you can doesn't mean you should. Or that you'll be happy with the result. Good Luck.
You have written a trigger that fires after or before a row change. This is in the middle of an execution. You cannot delete a row from the same table in that moment.
So you must write an after statement trigger instead that only fires when the whole statement has run.
create or replace trigger mytrigger
after update of exhibit_id on showing
begin
delete from showing where exhibit_id is null;
end mytrigger;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=dd5ade700d49daf14f4cdc71aed48e17
What you can do is create an extra column like is_to_be_deleted in the same table, and do this:
UPDATE SHOWING
SET EXHIBIT_ID = NULL, is_to_be_deleted = 'Y'
WHERE PAINT_ID = 5104;
You can use this parameter to implement your business logic of not showing the null details.
And later you can schedule a batch delete on that table to clean up these rows (or maybe archive it).
Benefit: you can avoid an extra unnecessary trigger on that table.
Nobody, will suggest you to use trigger to do this type of delete as it is expensive.

How to create TRIGGER with a reference to the triggered table?

Can I create an AFTER TRIGGER on a table and using that table in my SELECT query without getting mutating table error?
Example to a query I want to use.
This query will update number of times a certain status name is showing up in alert life cycle:
CREATE OR REPLACE TRIGGER COUNT_STEP
AFTER INSERT
ON STEPS
FOR EACH ROW
DECLARE
V_COUNT_SETP VARCHAR (10000);
BEGIN
SELECT COUNT (STATUS_NAME)
INTO V_COUNT_SETP
FROM (SELECT A.ALERT_ID, S.STATUS_NAME
FROM ALERTS A, ALERT_STATUSES S, STEPS ST
WHERE :NEW.ALERT_INTERNAL_ID = A.ALERT_INTERNAL_ID
AND ST.ALERT_STATUS_INTERNAL_ID = S.STATUS_INTERNAL_ID
AND S.STATUS_NAME IN ('Auto Escalate'))
GROUP BY ALERT_ID;
UPDATE ALERTS A
SET A.COUNT = V_COUNT_ESC
WHERE A.ALERT_INTERNAL_ID = :NEW.ALERT_INTERNAL_ID;
END;
/
The table I'm inserting a record to is also needed for counting the number of step occurrences since it's stores the alert id and all the steps id it had.
You need to be a bit more clearer in your questions. But, from what i understood, you need to create a trigger on a table, and perform a select for that same table. That gives you a mutanting table error. To bypass that, you need to perform a compound trigger on that table. Something like this:
create or replace trigger emp_ct
for insert on employees compound trigger
v_count number; -- Add variable here
before statement is
begin
-- PERFORM YOUR SELECT AND SEND TO A VARIABLE
end before statement;
after each row is
begin
-- DO WANT YOU WANTED TO DO. USE THE VARIABLE
end after each row;
end;
basically, with a compound trigger, you can capture every trigger event. By doing that, allows to query the table you're capturing.

SQL Error: ORA-04091: table is mutating while updating by a trigger

i am new to oracle , i am developing a hospital management system , i have this table to store patients :
create table patients(
p_id number not null primary key,
p_fullname full_name_ty,
p_gender char,
de_no number,
p_entry date ,
Diagnosis varchar2(25),
p_exit date,
constraint pdf foreign key (de_no) references department(dep_no)
);
where p_entry is the date when a patient enters the hospital , i made a trigger that calculates the residency time in the hospital on after the upadate of the (p_exit) date for the patient (setting this date means that the patient has left the hospital) , the trigger will simply calculate the difference between the two dates , and print it , here is the code of the trigger :
create or replace
trigger period_trig before update of p_exit on patients for each row
DECLARE
period Number(3);
enterr DATE;
exitt DATE;
BEGIN
enterr := :old.P_ENTRY;
exitt:= :NEW.P_EXIT;
Period :=exitt-enterr;
DBMS_OUTPUT.PUT_LINE('Duration:'||period);
update patients SET RESIDENCY= Period where P_ID = :old.P_ID;
end period_trig
put when i test the trigger and use an update statement like this :
update patients set p_exit = to_date('01/02/2001','dd/mm/yyyy') where p_id = 2;
and run it i get this error :
Error starting at line 1 in command:
update patients set p_exit = to_date('01/02/2001','dd/mm/yyyy') where p_id = 2
Error report:
SQL Error: ORA-04091: table SEM.PATIENTS is mutating, trigger/function may not see it
ORA-06512: at "SEM.UPDATEPAT", line 5
ORA-06512: at "SEM.PERIOD_TRIG", line 10
ORA-04088: error during execution of trigger 'SEM.PERIOD_TRIG'
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.
Error starting at line 1 in command:
update patients set p_exit = to_date('01/02/2001','dd/mm/yyyy') where p_id = 2
Error report:
SQL Error: ORA-04091: table SEM.PATIENTS is mutating, trigger/function may not see it
ORA-06512: at "SEM.PERIOD_TRIG", line 11
ORA-04088: error during execution of trigger 'SEM.PERIOD_TRIG'
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.
can anyone tell me how to fix it ? and thanks so much ..
You are modifying the very same table in the trigger that is currently being modified. As they error tells you:
*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.
There is actually no need in updating the table again, you can simply use a virtual column directly on the table that makes the entire trigger redundant:
CREATE TABLE patients(
p_id NUMBER NOT NULL PRIMARY KEY,
p_fullname VARCHAR2(255),
p_gender CHAR(1),
de_no NUMBER,
p_entry DATE,
Diagnosis VARCHAR2(25),
p_exit DATE,
RESIDENCY NUMBER GENERATED ALWAYS AS (p_exit-p_entry)
);
insert into patients (p_id, p_fullname, p_gender, de_no, p_entry, diagnosis) values (1, 'GVENZL', 'M', 1234, SYSDATE-1, 'healthy' );
commit;
select p_fullname, residency from patients;
update patients set p_exit = sysdate;
commit;
select p_fullname, residency from patients;

TRIGGER Oracle to prevent updating or inserting

I am having problems with this code below, which is a trigger used in Oracle SQL:
CREATE OR REPLACE TRIGGER TRG_TUTOR_BLOCK
BEFORE INSERT OR UPDATE ON tutors
FOR EACH ROW
DECLARE
BEGIN
IF :new.tutorName = :old.tutorName
THEN
RAISE_APPLICATION_ERROR(-20101, 'A tutor with the same name currently exists.');
ROLLBACK;
END IF;
END;
/
This trigger is used to prevent users from entering the same tutor name at different records.
After I insert two records with the same tutorname, the trigger does not block me from inserting it. Is there anyone can tell me what are the problems with this coding? Here are the sample format and insert values:
INSERT INTO tutors VALUES (tutorID, tutorName tutorPhone, tutorAddress, tutorRoom, loginID);
INSERT INTO tutors VALUES ('13SAS01273', 'Tian Wei Hao', '019-8611123','No91, Jalan Wangsa Mega 2, 53100 KL', 'A302', 'TianWH');
Trigger in Kamil's example will throw ORA-04091, you can see this with your own eyes here. ROLLBACK in a trigger is unnecessary, it runs implicitly when a trigger makes a statement to fail.
You can prohibit any DML on table by altering it with read only clause:
alter table tutors read only;
At last, integrity should be declarated with integrity constraints and not with triggers.
Good luck!
You don't need a trigger for this in Oracle.
You can do it with an "unique index" on the tutorName column (see http://docs.oracle.com/cd/B28359_01/server.111/b28310/indexes003.htm#i1106547).
Note: about your trigger, it fails on checking for another record with the same tutorName because it's not scanning the tutors table for another record with the same tutorName, it's just comparing the tutorName values of the row you are creating (in this case, old.tutorName is just NULL, because the row doesn't exist yet).
Check the case in yours trigger body
IF :new.tutorName = :old.tutorName
It returns true only if 'tutorName' value is the same in new and old record. When you'll trying to updat some value you'll get
IF 'someTutorName' = 'someTutorName'
which will return TRUE.
Inserting row cannot fire this rule because you're trying to compare something like that:
'someTutorName' = NULL
This case always returns FALSE.
Try to use something like that
CREATE OR REPLACE TRIGGER TRG_TUTOR_BLOCK
BEFORE INSERT OR UPDATE ON tutors
FOR EACH ROW
DECLARE
rowsCount INTEGER;
BEGIN
SELECT COUNT(*) FROM tutors WHERE tutorName is :new.tutorName INTO rowsCount;
IF rowsCount > 0
THEN
RAISE_APPLICATION_ERROR(-20101, 'A tutor with the same name currently exists.');
ROLLBACK;
END IF;
END;
/
But the best solution is the one mentioned by friol - use unique index by executing SQL like this
ALTER TABLE tutors
ADD CONSTRAINT UNIQUE_TUTOR_NAME UNIQUE (tutorName);
If you wanna completely ignore recording a row to a table you can follow these steps
rename table to something else and create a view with the same name and create an instead of trigger.
create table usermessages (id number(10) not null)
GO
alter table usermessages rename to xusermessages
GO
create or replace view usermessages as (select * from xusermessages)
GO
create or replace trigger usermessages_instead_of_trg
instead of insert or update on usermessages
for each row
begin
Null ;
end ;
GO
insert into usermessages(123)
Live test available here below
http://sqlfiddle.com/#!4/ad6bc/2

Resources