Creating an UPDATE trigger that causes the removal of the triggering row - oracle

I'm on Oracle Express 11g. I'm trying to create this trigger:
CREATE TRIGGER remove_useless_surveys
BEFORE UPDATE OF agent_id, agency_id, province_id ON surveys
FOR EACH ROW WHEN (
new.agent_id IS NULL AND
new.agency_id IS NULL AND
new.province_id IS NULL)
DELETE FROM surveys WHERE survey_code = :new.survey_code
agent_id, agency_id and province_id are foreign keys, with ON DELETE SET NULL clause.
I need a row in the surveys table to be DELETED when all the three foreign keys are set to NULL (because the referred rows are deleted).
The trigger compiles with no problems, but when the condition fires, then I get this error:
SQL error: ORA-04091: table REALESTATE.SURVEYS is mutating,
trigger/function may not see it ORA-06512: at
"REALESTATE.REMOVE_USELESS_SURVEYS", line 1 ORA-04088: error during
execution of trigger 'REALESTATE.REMOVE_USELESS_SURVEYS'
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.
Actually, I know I'm editing the same table that fired the trigger. But I want to cancel the update, or finalize it, I don't care, and then delete that row.
Can you help me? How can I achieve what I'm trying to do?

You could use a statement level trigger like this:
CREATE TRIGGER remove_useless_surveys
AFTER UPDATE OF agent_id, agency_id, province_id ON surveys
BEGIN
DELETE FROM surveys
WHERE agent_id IS NULL
AND agency_id IS NULL
AND province_id IS NULL;
END;

Related

Why AFTER UPDATE (FOR EACH ROW) trigger fires before (NOT DEFERRABLE) integrity constraint checks?

I am learning triggers using Oracle 12c HR sample schema and Oracle Develop PL/SQL Program Units student guide. Oracle says that the trigger execution model is:
Execute all BEFORE STATEMENT triggers.
Loop for each row affected by the SQL statement:
Execute all BEFORE ROW triggers for that row.
Execute the DML statement AND PERFORM INTEGRITY CONSTRAINT CHECKING FOR THAT ROW.
Execute all AFTER ROW triggers for that row.
Execute all AFTER STATEMENT triggers.
The sample HR schema has an EMPLOYEES table with a DEPARTMENT_ID FK recerencing a DEPARTMENT_ID PK in a DEPARTMENTS table.
Correctly, if I try to insert a record in EMPLOYEES having a FK value that does not exist among the PKs in DEPARTMENTS, I will get a ORA-02291: integrity constraint (HR.EMP_DEPT_FK) violated - parent key not found error.
As a proof of concept, I create the following BEFORE UPDATE trigger to insert the missing record in DEPARTMENTS and avoid the integrity constraint violation:
CREATE OR REPLACE TRIGGER employee_dept_fk_trg
BEFORE UPDATE OF department_id ON employees
FOR EACH ROW
BEGIN
INSERT INTO departments (department_id, department_name) VALUES(:new.department_id,'Dept '||:new.department_id);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
NULL; -- mask exception if department already exists
END;
It works fine (I can insert a non existent FK as the corresponding PK would be created first).
The studying material says then that we could make the HR.EMP_DEPT_FK constraint DEFERRABLE INITIALLY DEFERRED in order to postpone the integrity check at the moment of the final COMMIT and not at the end of each INSERT statement (see item #4 in list), and so create an AFTER UPDATE trigger rather than a BEFORE UPDATE one like the previous.
Are there any advantages of doing so versus just using a BEFORE UPDATE trigger?
Before altering the HR.EMP_DEPT_FK constraint setting it DEFERRABLE INITIALLY DEFERRED, I wanted to verify the assertion in item #4, so I created an identical AFTER UPDATE trigger and I tried to insert a non existent FK in the employees table: I RECEIVED NO ORA-02291 ERROR and the new PK record in DEPARTMENTS table has been created! It seems that the AFTER UPDATE trigger executes before any integrity constraint check, while that should happen after! (see item #5)
Could you help me on this topic, please?
Thank you all,
Pino

If a trigger on an Oracle DB table fails to execute successfully or does not compile, can it impact the base table?

let's say there are tables a and b.
There is an AFTER INSERT trigger on a, that copies the row data to b.
If, during the execution of the trigger, there is an error does it impact table a in any way?
If the trigger does not compile, because it's ill-defined, does it impact table a?
I want to add a trigger to a table that is 'not mine'. I want to evaluate the risk that this can potentially pose.
Cheers
============== edit ================
I verified that - by handling the error (as suggested in the reply) - it now does not impact the base table.
create table tableA (column1 number);
create table tableB (column1 number, CONSTRAINT constraintName PRIMARY KEY (column1));
create or replace TRIGGER tableA_trigger
AFTER INSERT ON tableA
FOR EACH ROW
DECLARE
--
BEGIN
insert into tableB values (1);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error occured but ignored.');
END;
insert into tableA values (1);
insert into tableA values (1);
After that, tableA had two records, tableB only 1.
Without the exception handling, both tables would only have one record each, and after the first insert it would have shown an exception in the SQL Developer window.
Yes, it will impact the base table.
If there is error in insert trigger it will not allow to insert any record in base table. Same applies to all type of triggers.
Also, Adding DML triggers to tables affects the performance of DML statements on those tables.
According to oracle documentation:
If a predefined or user-defined error condition or 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. User-defined exceptions are commonly used in
triggers that enforce complex security authorizations or integrity
constraints.

Trigger on Delete (Before/After) is created successfully but on deletion of data gives an error

I have two tables here, one of Employees and other of Departments. The department table's primary key is acting as a foreign key in Employees table.
What I am trying to do here is this that whenever the user deletes a record from Department table, it first deletes all the employees of that department from the employees table and then delete the row from the department table. Since it must delete from employees table first I am using Before trigger here.
This is the trigger I created (and it compiled successfully)
create or replace trigger delete_department_employees
before delete
on dept
for each row
begin
delete from emp
where deptno = :old.deptno;
end;
/
Now, I wrote this command to delete the record from department table,
delete from dept
where deptno = 10
But, I got this error:
ORA-02292: integrity constraint (HASSANASHAS.SYS_C007000) violated - child record found
ORA-06512: at "HASSANASHAS.DELETE_DEPARTMENT_EMPLOYEES", line 2
ORA-04088: error during execution of trigger 'HASSANASHAS.DELETE_DEPARTMENT_EMPLOYEES'
Just to be clear, I also tried using after delete instead of before delete, and that time also I was faced with same error.
Can anyone explain the reasoning behind this error and also how can I solve it? Thank you in advance!

Reading current table id during trigger operation

I have three tables: A and B
The relationship is A can have many B
So B has a reference to the A.id as one of its columns
Table A
|id|date|...
Table B
|id|A_id|...
I have created an Oracle trigger on table A so that when it is updated it updates an A_Mod table.
This trigger is
CREATE OR REPLACE TRIGGER TR_A_INSERT_UPDATE
AFTER INSERT OR UPDATE ON A
FOR EACH ROW
BEGIN
INSERT INTO A_Mod values(..., :new.date, ...)
END;
This works fine :)
My problem si creating the trigger for table B.
The trigger is:
CREATE OR REPLACE TRIGGER TR_B_INSERT_UPDATE
AFTER INSERT OR UPDATE ON B
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT aa.date INTO ts FROM B bb
INNER JOIN A aa ON a.id = bb.A_id
WHERE bb.id = :new.id;
INSERT INTO A_Mod values(..., :new.date, ...)
END;
This trigger is reading the ID of the updated line in table B and then getting the date from the corresponding row in table A. It then tries to insert it into A_Mod
The problem is I get a mutating error
Error report:
SQL Error: ORA-04091: table B is mutating, trigger/function may not see it
ORA-06512: at "TR_B_INSERT_UPDATE", line 5
ORA-04088: error during execution of trigger 'TR_B_INSERT_UPDATE'
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.
Looking at the docs I can remove this error by removing the FOR EACH ROW line and having the trigger fire once per statement rather than once per row. Unfortunately I am using an ORM mapper so do not control how the updates happen. I think there would be times when the update could cover multiple rows.
The docs say something about creating a temporary table but I am not sure how this would help. Would I have to create a temporary table inside the trigger, create a trigger on this temporary table which then updates A_Mod, update this temporary table when the trigger is fired and then delete everything after?
Any tips greatly appreciated.
Thanks
It doesn't appear that there is any reason in your trigger on B to query the B table. You should be able to simply query A using the :new.a_id
CREATE OR REPLACE TRIGGER TR_B_INSERT_UPDATE
AFTER INSERT OR UPDATE ON B
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT a.date
INTO ts
FROM A a
WHERE a.id = :new.a_id;
INSERT INTO A_Mod values(..., :new.date, ...)
END;
From a data modeling standpoint, however, I would be very concerned if a trigger on a child table needed to query any information from the parent table or to insert data into the same history table that the parent table writes to. That seems likely to indicate a normalization issue.

After Trigger execute before constraint check in oracle

I have After Insert/Update trigger on Table T1 which get the referential data for Col1 from T2 and does some work and insert it into another table.
The col1 is FK to Table T2.
When user insert the incorrect or non existing value into the Col1 and if trigger is disabled I am getting constraint error that is fine.
But when trigger is enabled and user insert the wrong value in Col1 trigger is getting fired and shows the 'no data found' error message.
Actually I am expecting the table to throw constraint error, but trigger is throwing it.
Please let me know your comments about this trigger behaviour.
You do not mention whether you are using BEFORE or AFTER triggers. Please check the documentation for the order of execution:
BEFORE statement
BEFORE row
CONSTRAINTS
AFTER row
AFTER statement
I'm guessing the trigger would have to be a BEFORE trigger. It will run prior to the constraints being checked. If the trigger raises NO_DATA_FOUND, then the constraint never gets checked. If the trigger is disabled, it is not run, so the constraint gets checked.

Resources