how to convert a mutating trigger to a stored procedure - sql

I have the following trigger:
CREATE OR REPLACE TRIGGER planning_trig BEFORE
UPDATE OF planned_remediation_date ON evergreen
FOR EACH ROW
DECLARE
planned_remediation_date DATE;
BEGIN
SELECT
planned_remediation_date
INTO planned_remediation_date
FROM
evergreen
WHERE
hostname = :new.hostname
AND instance_name = :new.instance_name
AND product_home = :new.product_home
AND it_service = :new.it_service
AND sw_name = :new.sw_name;
IF
planned_remediation_date IS NOT NULL
AND planned_remediation_date > trunc(sysdate)
THEN
UPDATE evergreen
SET
planning_status = 'planned';
ELSE
UPDATE evergreen
SET
planning_status = 'overdue';
END IF;
END;
/
After an update of the row in my table evergreen I get this error:
ORA-04091: table PTR.EVERGREEN is mutating, trigger/function may not see it
I believe the error comes from the fact that I'm trying to update the very same table the trigger is firing on. I read that the best way to handle updates in the same table are stored procedures but I'm quite new to oracle and don't know how to achieve that. Another possibility I heard is AUTONOMOUS_TRANSACTION Pragma but not sure either if this applies to my case.
A little more background, the underlying table has a composite unique key (hostname,instance_name,product_home,it_service,sw_name) and I want to update an existing column on this table called planning_status based on the updated value of planned_remediation_date. If that value is not null and greater then today then update with planned else overdue.

Expression planned_remediation_date IS NOT NULL is redundant. planned_remediation_date > trunc(sysdate) is never TRUE when planned_remediation_date is NULL.
Your trigger can be written much shorter:
CREATE OR REPLACE TRIGGER planning_trig
BEFORE UPDATE OF planned_remediation_date ON evergreen
FOR EACH ROW
BEGIN
IF :new.planned_remediation_date > trunc(sysdate) THEN
:new.planning_status := 'planned';
ELSE
:new.planning_status := 'overdue';
END IF;
END;
/
I guess you like to modify the updated row, not other rows in that table. Otherwise you would need a procedure or a COMPOUND trigger

Related

How to change from oracle trigger to oracle compound trigger?

I have to change from given Oracle trigger:
CREATE OR REPLACE TRIGGER MY_TRIGGER
AFTER UPDATE OF STATUS ON T_TABLE_A
FOR EACH ROW
BEGIN
UPDATE T_TABLE_B T
SET T.STATUS = :NEW.STATUS
WHERE T.REF_ID = :NEW.ID;
END;
/
to an Oracle compound trigger. Effect must be the same. My approach is now:
CREATE OR REPLACE TRIGGER MY_NEW_TRIGGER
for insert or update on T_TABLE_A
compound trigger
before statement -- STUB
is
begin
null;
end before statement;
before each row
is
begin
end before each row;
after each row -- STUB
is
begin
--IDEA: collect ids of changed records (T_TABLE_A) here >> in a global variable? array?
end after each row;
after statement -- STUB
is
begin
--IDEA: Bulk Update of T_TABLE_B (goal is: update T_TABLE_B.STATUS column; must be the same as T_TABLE_A.STATUS)
end after statement;
end;
/
But as a Java Developer I am very slow to find out the correct syntax of variables, arrays and simple DB scriptings, so any approach is helpful.
Approach where to start is marked as "IDEA".
As I can see you have almost done the half of the job , I amended the code to IDEA part.
I have not included the before statement part and also the triggering clause and all you can adjust according to your need. I just considered for updating the status column in my code.
CREATE OR REPLACE TRIGGER my_trigger
FOR UPDATE OF status ON t_table_a
COMPOUND TRIGGER
-- record type to hold each record updated in t_table_a
-- columns we are intersted in are id and status
TYPE table_a_rec IS RECORD(
id t_table_a.id%TYPE
,status t_table_a.status%TYPE);
--table type based on record to access each record using this
TYPE table_a_row_data_t IS TABLE OF table_a_rec INDEX BY PLS_INTEGER;
-- global variable for the compound trigger
g_row_level_data table_a_row_data_t;
AFTER EACH ROW IS
BEGIN
-- IDEA: collect ids of changed records (T_TABLE_A) here >> in a global variable? array?
g_row_level_data(g_row_level_data.count + 1).id := :new.id;
g_row_level_data(g_row_level_data.count).status := :new.status;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
--IDEA: Bulk Update of T_TABLE_B (goal is: update T_TABLE_B.STATUS column; must be the same as T_TABLE_A.STATUS)
FORALL i IN 1 .. g_row_level_data.count
UPDATE t_table_b t
SET t.status = g_row_level_data(i).status
WHERE t.ref_id = g_row_level_data(i).id;
END AFTER STATEMENT;
END my_trigger;
/

Why does it raise a mutating table error?

I am attenting to create a trigger that before delete a row in the table CLUBS check if the column END_DATE is null, if it's null the row instead of deleting the row it replaces the value with sysdate and if is not null the row doesn't get deleted.
CREATE OR REPLACE TRIGGER TRANSFER_DATA
BEFORE DELETE ON CLUBS
FOR EACH ROW
BEGIN
IF :old.END_DATE IS NULL
THEN UPDATE CLUBS SET END_DATE = SYSDATE;
ELSE
RAISE_APPLICATION_ERROR(-20033, 'No se puede borrar');
END IF;
END;
I've tried this code but it raises an error:
"table %s.%s is mutating, trigger/function may not see it"
How can I make it work?
By the way, I am working on Oracle sqldeveloper
You need to create a view and a INSTEAD OF trigger:
create or replace view v_CLUBS as
SELECT * FROM CLUBS; -- perhaps add 'WHERE END_DATE IS NULL'
CREATE OR REPLACE TRIGGER TRANSFER_DATA
INSTEAD OF DELETE ON V_CLUBS
FOR EACH ROW
BEGIN
IF :old.END_DATE IS NULL THEN
:new.end_date := sysdate;
ELSE
RAISE_APPLICATION_ERROR(-20033, 'No se puede borrar');
END IF;
END;
Processing through a view and an instead of trigger is differently an effective way to go. It seems your prior attempt approached the task incorrectly. A simple view is fully updateble (generally). But in this case you want to intercept the delete and apply the action to the underlying table. Assuming CLUBS is view you process against and CLUBS_TBL is the actual table the trigger you want is:
-- create trigger to process deletes on the view
create or replace trigger del_clubs_isd
instead of delete on clubs
begin
update clubs_tbl
set end_date = sysdate
where id = :old.id;
end del_clubs_isd;
See a full script at fiddle.
Further, as presented the user never needs to know the club is not actually deleted, but just marked to appear that way.

PL/SQL ORACLE: Trigger updates on delete

I am trying to make a trigger that increases the booked value and decreases the available value whenever new record is inserted inside the table ticket_price. If a record is deleted, I want it to decrease the booked value and increase the available value.
Although I am able to successfully make the trigger work for INSERT, I am unable to do the same for updating the values on deletion of a record.T his is the error I get whenever I try to delete a record
ORA-01403: no data found
ORA-06512: at "K.CAL", line 6
ORA-04088: error during execution of trigger 'K.CAL'
Just to clarify, I am updating values in another table, not the same table I am deleting!
Here is my code for the trigger:
CREATE OR REPLACE TRIGGER cal
BEFORE INSERT OR DELETE ON TICKET_PRICE FOR EACH ROW
DECLARE
V_TICKET TICKET_PRICE.TICKETPRICE%TYPE;
V_BOOKED FLIGHTSEAT.BOOKED_SEATS%TYPE;
V_AVAILABLE FLIGHTSEAT.AVAILABLE_SEATS%TYPE;
BEGIN
SELECT BOOKED_SEATS,AVAILABLE_SEATS
INTO V_BOOKED,V_AVAILABLE
FROM FLIGHTSEAT
WHERE SEAT_ID=:NEW.SEAT_ID;
IF INSERTING THEN
V_BOOKED:=V_BOOKED+1;
V_AVAILABLE:=V_AVAILABLE-1;
UPDATE FLIGHTSEAT
SET BOOKED_SEATS=V_BOOKED, AVAILABLE_SEATS=V_AVAILABLE
WHERE SEAT_ID=:NEW.SEAT_ID;
ELSIF DELETING THEN
V_BOOKED:=V_BOOKED-1;
V_AVAILABLE:=V_AVAILABLE+1;
UPDATE FLIGHTSEAT
SET BOOKED_SEATS=V_BOOKED, AVAILABLE_SEATS=V_AVAILABLE
WHERE SEAT_ID=1;
END IF;
END;
You have correctly surmised that :new.seat is not available on the update for a delete. But neither is it available for the select and ow could you know sea_id=1 was need to be updated? For reference to Deleted row data use :Old.column name; is this case use :old_seat_id for both select and update.
But you don't need the select at all. Note: Further you have an implied assumption that seat_id is unique. I'll accept that below.
create or replace trigger cal
before insert or delete on ticket_price
for each row
declare
v_seat_id flightseat.seat_id%type;
v_booked flightseat.booked_seats%type;
begin
if INSERTING then
v_booked := 1;
v_seat_id := :new.seat_id;
else
v_booked := -1;
v_seat_id := :old.seat_id;
end if;
update flightseat
set booked_seats=booked_seats+v_booked
, available_seats=available_seats-v_booked
where seat_id = v_seat_id;
end;

Oracle update trigger issue

Question is I have the following trigger to watch my specific column "CHANGED" if its updated to a 1 I would like it to update the same row and the column "CHANGED_DATE" to the current SYSDATE. For some reason I keep getting this error any help would be greatly appreciated.
Error
UPDATE "SYSADM"."SHIP_CALENDAR" SET CHANGED = '1' WHERE ROWID = 'AABCxCAAEAAAKYEAAB' AND ORA_ROWSCN = '6165377066'
ORA-04091: table SYSADM.SHIP_CALENDAR is mutating, trigger/function may not see it
ORA-06512: at "SYSADM.SHIP_CALENDAR_CHANGED", line 5
ORA-04088: error during execution of trigger 'SYSADM.SHIP_CALENDAR_CHANGED'
Trigger
create or replace TRIGGER SHIP_CALENDAR_CHANGED
AFTER UPDATE
OF CHANGED
ON SHIP_CALENDAR
REFERENCING OLD AS o NEW AS n
FOR EACH ROW
DECLARE
BEGIN
-- UPDATE SHIP CALENDAR SET UPDATE_CHANGE TO SYSDATE IF CHANGED CHANGES
IF :n.CHANGED = '1' then
UPDATE SHIP_CALENDAR
SET CHANGED_DATE = SYSDATE
WHERE SHIPMENT_ID = :o.SHIPMENT_ID;
END IF;
END;
Table
Use a before update trigger:
create or replace TRIGGER SHIP_CALENDAR_CHANGED
BEFORE UPDATE
OF CHANGED
ON SHIP_CALENDAR
REFERENCING OLD AS o NEW AS n
FOR EACH ROW
DECLARE
BEGIN
-- UPDATE SHIP CALENDAR SET UPDATE_CHANGE TO SYSDATE IF CHANGED CHANGES
IF :n.CHANGED = '1' then
:n.CHANGED_DATE := SYSDATE
END IF;
END;

Create Trigger to reorder a column and modify statement levels

I need to Alter the Product table to have a varchar2(3) reorder column and modify the statement level trigger from the notes to set the product reorder field to “yes” if the quantity on hand is less than twice the product min quantity or if the quantity on hand is less than 10. The value should be “no” otherwise. The trigger is fired after an insert or an update to p_min or p_qoh. Debug by checking the data beforehand, making a change, then checking afterwards.
For this I have
CREATE or REPLACE TRIGGER
TRG_Product_Reorder
AFTER INSERT OR UPDATE of P_min, P_qoh ON lab9_Product
BEGIN
UPDATE lab9_Product SET REORDER = 'yes'
WHERE P_qoh < P_min*2 or p_qoh < 10;
UPDATE lab9_Product SET REORDER = 'no'
WHERE P_qoh >= p_min*2;
END;
/
I get the error:
SQL statement ignored
"REORDER": invalid identifier
I think you might want a before trigger rather than an after trigger. It would be something like this:
CREATE or REPLACE TRIGGER
TRG_Product_Reorder
BEFORE INSERT OR UPDATE of P_min, P_qoh ON
lab9_Product
BEGIN
IF :OLD.P_qoh < :OLD.P_min*2 AND :OLD.p_qoh < 10 THEN
:NEW.REORDER := 'yes';
ELSE :NEW.REORDER := 'no';
END IF;
END;