PL/SQL Trigger throws a MUTATING TABLE error - plsql

I have this trigger:
create or replace TRIGGER TR14_2
BEFORE INSERT OR UPDATE OF CANTIDAD ON DISTRIBUCION
FOR EACH ROW
DECLARE
total_cars NUMBER;
total_cars_potential NUMBER;
BEGIN
SELECT sum(cantidad) into total_cars
FROM DISTRIBUCION
WHERE cifc = :NEW.cifc;
total_cars_potential := total_cars + :NEW.cantidad;
IF INSERTING THEN
IF(total_cars_potential > 40) THEN
raise_application_error(-20005, 'Dealer [' || :NEW.cifc || '] already has 40 cars stocked');
END IF;
END IF;
IF UPDATING THEN
IF(total_cars_potential - :OLD.cantidad > 40) THEN
raise_application_error(-20006, 'That update of CANTIDAD makes the dealer exceeds the limit of 40 cars stocked');
END IF;
END IF;
END;
It gets a mutating table error, and I have checked that is because of the UPDATING block of code, the INSERTING goes ok; but why? And how can I fix it?
Just to clarify, I want that each dealer can have at maximum 40 cars stocked. So, if I add a row to DISTRIBUCION ("distribution") with cantidad ("quantity") that will make the dealer exceed its maximum stock, I will raise an error.
But, if I update a quantity of cars of a type, stocked already in the database, and I exceed 40 cars, I want also a exception to be thrown.
Thing is, I am not seeing the mutatig table error on the UPDATING block.

1st: The reason you get a mutating table syndrome is that you're reading from the table that is updated in the trigger (selecting the total cars).
2nd: Solution: I'd probably create 2 triggers: A for-each-row trigger as you did that collects the updated rows in a package variable
CREATE OR REPLACE PACKAGE PCK_TR14_2 IS
TYPE changed_row IS RECORD (
cantidad DISTRIBUCION.cantidad%TYPE,
);
TYPE changed_row_table IS TABLE OF changed_rows INDEX BY binary_integer;
changed_rows changed_row_table;
cnt_changed_rows BINARY_INTEGER DEFAULT 1;
END PCK_TR14_2;
/
The for-each-row trigger would look something like this (now an after insert!)
create or replace TRIGGER TR14_2
AFTER INSERT OR UPDATE OF CANTIDAD ON DISTRIBUCION
FOR EACH ROW
DECLARE
BEGIN
PCK_TR14_2.changed_rows(PCK_TR14_2.cnt_changed_rows).cantidad := :old.cantidad;
PCK_TR14_2.cnt_changed_rows := PCK_TR14_2.cnt_changed_rows + 1;
END;
/
Then in a after statement trigger implement your logic:
CREATE OR REPLACE TRIGGER TR14_2_S
AFTER INSERT OR UPDATE OF CANTIDAD ON DISTRIBUCION
BEGIN
FOR i IN PCK_TR14_2.changed_rows.FIRST..PCK_TR14_2.changed_rows.LAST LOOP
-- YOUR LOGIC HERE
null;
END LOOP;
END;
/
Access the candidat in the "virtuell" table as needed ( PCK_TR14_2.changed_rows(i). CANTIDAD)

A trigger can not change a table that it has read from. This is the mutating table error issue.
You can see the solutions provided in the below link
Mutating table error
avoiding_mutating_table_error

Related

PL/SQL not functioning

Im currently trying to implement a row trigger that fires when the new Employee number inserted into the table is not continuous.
"Continuous" in a relationship to the Employee number means the first record inserted will have the Employee number 1, the second record will have the employee number 2, and each next position must have a number greater by one that a number of the previous position.
I have successfully created the trigger, however when I inserted a new record that have an Employee number that is not continuous, my trigger is not fired.
Im unsure where I went wrong and hope I can get some explanation and corrections on my code.
CREATE OR REPLACE TRIGGER CONTENUM
AFTER INSERT ON TRKEMPLOYEE
FOR EACH ROW
DECLARE
continuous_value EXCEPTION;
PRAGMA exception_init(continuous_value, -20111);
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
IF (:NEW.E# > :OLD.E# + 1) THEN
RAISE_APPLICATION_ERROR (-20111,'The value of Employee number must be continuous');
END IF;
END CONTENUM;
/
Here is the format of my sample TRKEMPLOYEE table
CREATE TABLE TRKEMPLOYEE(
E# NUMBER(12) NOT NULL,
NAME VARCHAR(50) NOT NULL,
DOB DATE ,
ADDRESS VARCHAR(300) NOT NULL,
HIREDATE DATE NOT NULL,
CONSTRAINT TRKEMPLOYEE_PKEY PRIMARY KEY(E#) );
Here is my insert statement.
Currently in my table TRKEMPLOYEE there is only 15 rows thus with my insert statement, the trigger should fire but it is not happening.
INSERT INTO TRKEMPLOYEE VALUES( 17, 'David', NULL, 'GB',sysdate );
Thank you.
First of all you are checking AFTER INSERT ON TRKEMPLOYEE which will be executed after the row is inserted.
Secondly, you cannot check :OLD.E# since you are not updating and you are not using a old value.
Also you should drop the trigger at all and use SEQUENCES and let Oracle take care of the auto-increment values every time you add a new employee.
If you want to continue with your current logic, fixes that can be applied:
Change AFTER INSERT ON TRKEMPLOYEE to BEFORE INSERT ON TRKEMPLOYEE
Logic should be changed as below:
CREATE OR REPLACE TRIGGER contenum BEFORE
INSERT ON trkemployee
FOR EACH ROW
DECLARE
continuous_value EXCEPTION;
PRAGMA exception_init ( continuous_value, -20111 );
PRAGMA autonomous_transaction;
max_e# INTEGER;
BEGIN
SELECT
nvl(MAX(e#), 0)
INTO max_e#
FROM
trkemployee;
IF ( :new.e# > max_e# + 1 ) THEN
raise_application_error(-20111, 'The value of Employee number must be continuous');
END IF;
END contenum;
/
I do not recommend this solution because it will start to become slower as your table starts to grow.

Compound Triggers

Problem - Want to avoid the problem of mutating triggers by using the compound trigger. But unable to do so
Background -
I want to insert data in new table " Tracking Table " whenever there is change in Main table "CUSTOM_ITEM"
Design is such that, everytime a row is created in table an ITEM_ID is generated but there is a column FIRST_ITEM_ID that remains same in some cases.
So whenever a new row is added, I want to check its FIRST_ITEM_ID and then check the whole table and find out all the ITEM_IDs having that same FIRST_ITEM_ID.
And I want to insert all those rows in the New table using trigger.
Is it even possible ?
Attaching the trigger :
CREATE OR REPLACE TRIGGER APP.TEST_TRG
FOR DELETE OR INSERT OR UPDATE
ON APP.CUSTOM_ITEM
COMPOUND TRIGGER
TYPE t_change_tab IS TABLE OF APP.TEST_TRG.OBJECT_ID%TYPE;
g_change_tab t_change_tab := t_change_tab();
BEFORE EACH ROW IS
BEGIN
Select item_id bulk collect into g_change_tab from CUSTOM_ITEM where first_item_id =
(Select first_item_id from CUSTOM_ITEM where item_id = :NEW.item_id);
For i in 1 .. g_change_tab.COUNT()
LOOP
g_change_tab.extend;
END LOOP;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
For i in 1 .. g_change_tab.COUNT()
LOOP
app.bc_acs_pkg.populate_TEST_TRG /* Package Inserts data */
(p_object_type => 'ITEM',
p_object_id => g_change_tab(i));
END LOOP;
g_change_tab.delete;
END AFTER STATEMENT;
END ;
/
You can do what you want just not with your current approach. Let's take a step back. What is a mutating table exception (ORA-04091). It is thrown when you attempt to access the table on which the trigger fired in a row level event, that is not permitted. Just creating a compound trigger does not remove that restriction. So in your Before Row segment the statement
Select item_id
bulk collect into g_change_tab
from CUSTOM_ITEM where first_item_id =
(Select first_item_id from CUSTOM_ITEM where item_id = :NEW.item_id);
is invalid, and results in raising ORA-04091. What you need is to just build your collection with the necessary ids. Then process them in the After statement segment.
create or replace trigger test_trg
for delete or insert or update
on custom_item
compound trigger
type t_change_tab is
table of custom_item.first_item%type;
g_change_tab t_change_tab := t_change_tab();
before each row is
l_first_item_exists boolean := false;
indx integer;
begin
indx := g_change_tab.first;
while not l_first_item_exists
and indx is not null
loop
l_first_item_exists := (g_change_tab(indx) = :new.first_item);
if not l_first_item_exists
then
indx := g_change_tab.next(indx);
end if;
end loop;
if not l_first_item_exists
then
g_change_tab.extend;
g_change_tab(g_change_tab.last) := :new.first_item;
end if;
end before each row;
after statement is
begin
for indx in g_change_tab.first .. g_change_tab.last
loop
insert into tracking_table(item_id, first_item)
select item_id, first_item
from custom_item
where first_item = g_change_tab(indx);
end loop;
end after statement;
end test_trg;
The issue here is the loops, always the slowest processing, and very bad in triggers. Below is an approach which avoids them totally. It does however require creating your type array at the schema level.
create or replace type custom_item_change_t is
table of integer ; --custom_item.first_item%type;
create or replace trigger test_trg
for insert
on custom_item
compound trigger
g_change_tab custom_item_change_t := custom_item_change_t();
before each row is
begin
g_change_tab.extend;
g_change_tab(g_change_tab.last) := :new.first_item;
end before each row;
after statement is
begin
insert into tracking_table(item_id, first_item)
select item_id, first_item
from custom_item ci
where first_item in (select distinct column_value
from table(g_change_tab)
)
and not exists
( select null
from tracking_table tt
where ci.item_id = tt.item_id
and ci.first_item = tt.first_item
);
end after statement;
end test_trg;

Creating a Database Trigger that checks if more than one record was added on a date?

CREATE OR REPLACE TRIGGER POSITION_NUMBER
BEFORE UPDATE OR INSERT OR DELETE ON APPLIES
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
NUMBER_OF_POSITIONS NUMBER;
BEGIN
SELECT count(pnumber) INTO NUMBER_OF_POSITIONS
FROM APPLIES WHERE anumber = :NEW.anumber;
IF( NUMBER_OF_POSITIONS > 2 AND count(APPDATE) > 2 )
THEN
RAISE_APPLICATION_ERROR(-20000,'an Employee cannot apply for
more than two positions');
END IF;
END;
/
Im attemtping to create a trigger that goes off if an Applicant applys for more than two Positions on the Same Day, but im not sure how i would implement the Date side of it. Below is the set of relational Schemeas
You can use the TRUNC function to remove the time portion and then see if the application date matches today's date, regardless of time.
Also, there is no need for the autonomous transaction pragma. You are not executing any DML.
CREATE OR REPLACE TRIGGER position_number
BEFORE UPDATE OR INSERT OR DELETE
ON applies
DECLARE
number_of_positions NUMBER;
BEGIN
SELECT COUNT (pnumber)
INTO number_of_positions
FROM applies
WHERE anumber = :new.anumber AND TRUNC (appdate) = TRUNC (SYSDATE);
IF number_of_positions > 2
THEN
raise_application_error (
-20000,
'An Employee cannot apply for more than two positions on the same day');
END IF;
END;
/

The main thing is to create a database trigger that the price in my room table where the type is double must be greater than 100

CREATE OR REPLACE TRIGGER PriceMust
BEFORE INSERT ON ROOM
REFERENCE NEW AS new
FOR EACH ROW
BEGIN
SELECT price, type
From Room
Where type =: 'Double'
IF price<=: 100
raise_application_error('Price Must Be Over 100');
END IF;
END;
/
OK, I'll give you a cleaned up version:
CREATE OR REPLACE TRIGGER PriceMust
BEFORE INSERT ON ROOM
REFERENCING NEW AS new
FOR EACH ROW
BEGIN
IF :NEW.TYPE = 'Double' and :NEW.price <= 100 THEN
raise_application_error(-20001, 'Price Must Be Over 100');
END IF;
END PRICEMUST;
/
The trigger is on the ROOM table, so you can't select from ROOM inside the trigger without getting a MUTATING TABLE error. Instead, just use the value in NEW.TYPE to determine the room type.
Best of luck.

Table is mutating, trigger/function may not see it

I create a trigger to check COUNT.
create or replace
TRIGGER TEST_TRG before INSERT OR UPDATE ON TEST
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
AVAILABLE INTEGER;
BEGIN
IF UPDATING THEN
IF(:new.STATUS = 600 OR :new.STATUS = 700) THEN
SELECT COUNT(1) INTO AVAILABLE FROM TEST T WHERE T.IDDISPLAY = :new.IDDISPLAY
AND T.STATUS NOT IN (600,700);
IF(AVAILABLE = 0) THEN
InsertOrUpdateAnotherTable(:new.IDDISPLAY, :new.STATUS, 0);
ELSE
RETURN;
END IF;
END IF;
END IF;
After I change the status to 600 in Test Table, there are errors.
ORA-04091: table USER.TEST is mutating, trigger/function may not see it
ORA-06512: at "USER.TEST_TRG ", line 8
ORA-04088: error during execution of trigger 'USER.TEST_TRG'
I will insert or update another table if the condition is met.
The error is because I try to get COUNT of the current table when the trigger is triggering the same table. This will cause mutating happens.
I have try transaction_anonymous and Compound Trigger, but at the end still same error occurs.
Anyone can help me for another solution, please.
Firstly:
This below line
IF(AVAILABLE = 0) THEN
never will be reached, even if you could eliminate mutating error.
Checking count like this: COUNT(1) INTO AVAILABLE will throw exception "no data found" when there is really no records you're quering.
Secondly:
using compund trigger with section AFTER EACH ROW let you avoid mutating as Tom wrote http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm:
Try this:
create or replace
TRIGGER TEST_TRG FOR INSERT OR UPDATE ON TEST
COMPOUND TRIGGER
AVAILABLE INTEGER;
AFTER EACH ROW IS
BEGIN
IF UPDATING THEN
IF(:new.STATUS = 600 OR :new.STATUS = 700) THEN
BEGIN
SELECT COUNT(1) INTO AVAILABLE FROM TEST T WHERE T.IDDISPLAY = :new.IDDISPLAY
AND T.STATUS NOT IN (600,700);
EXCEPTION
WHEN NO_DATA_FOUND THEN
InsertOrUpdateAnotherTable(:new.IDDISPLAY, :new.STATUS, 0);
END;
END IF;
END IF;
END AFTER EACH ROW;
END TEST_TRG;

Resources