Oracle database: Disabling Trigger updates when a specific column is altered? - oracle

I have the following existing trigger on the PERSON table in my WORKPLACE schema.
Currently, It updates the last_updated column automatically to the current date when any other column is altered.
Trigger:
CREATE OR REPLACE TRIGGER "WORKPLACE"."UPD_PERSON"
BEFORE update ON person
FOR each row
BEGIN
:new.last_updated := systimestamp;
END;
Current table:
ID | Name | Created | last_updated | checked
I have a column within my table named 'checked' , how can I changed this trigger so that when the 'checked' column is changed, that the last_updated column is NOT changed.

From the documentation:
"An UPDATE statement might include a list of columns. If a triggering statement includes a column list, the trigger is fired only when one of the specified columns is updated"
So insert a columns list for all columns except checked.
CREATE OR REPLACE TRIGGER "WORKPLACE"."UPD_PERSON"
BEFORE update of ID , Name , Created ON person
FOR each row
BEGIN
:new.last_updated := systimestamp;
END;

You can use a simple IF condition. "how can I changed this trigger so that when the 'checked' column is changed, that the last_updated column is NOT changed" means "update column last_updated only if 'checked' column is not changed"
CREATE OR REPLACE TRIGGER "WORKPLACE"."UPD_PERSON"
BEFORE update ON person
FOR each row
BEGIN
IF :old.checked = :new.checked THEN
:new.last_updated := systimestamp;
END IF;
END;
Note, in case checked can be NULL, you have to verify also on NULL values, e.g.
IF :old.checked = :new.checked or (:old.checked IS NULL AND :new.checked IS NULL) THEN
You can put condition also in header like this (note the missing colon : in condition)
CREATE OR REPLACE TRIGGER "WORKPLACE"."UPD_PERSON"
BEFORE update ON person
FOR each row
WHEN (old.checked = new.checked)
BEGIN
:new.last_updated := systimestamp;
END;

Related

Accessing old and new values without :OLD and :NEW in a trigger

As discussed here, I'm unable to use :OLD and :NEW on columns with collation other than USING_NLS_COMP. I'm trying to find a way around this but haven't been successful so far.
This is the original trigger:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
AFTER UPDATE ON PERSONS
FOR EACH ROW
begin
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := :old.SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := :new.SalutationTitle;
end;
This is what I've tried:
CREATE OR REPLACE TRIGGER SYS$PERSONSSALUTATIONAU
FOR UPDATE ON Persons
COMPOUND TRIGGER
TYPE Persons_Record IS RECORD (
SalutationTitle NVARCHAR2(30)
);
TYPE Persons_Table IS TABLE OF Persons_Record INDEX BY PLS_INTEGER;
gOLD Persons_Table;
gNEW Persons_Table;
BEFORE EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gOLD
FROM Persons
WHERE ID = :OLD.ID;
END BEFORE EACH ROW;
AFTER EACH ROW IS BEGIN
SELECT SalutationTitle
BULK COLLECT INTO gNEW
FROM Persons
WHERE ID = :NEW.ID;
END AFTER EACH ROW;
AFTER STATEMENT IS BEGIN
FOR i IN 1 .. gNEW.COUNT LOOP
State_00.Salutations_ToDelete(State_00.Salutations_ToDelete.Count + 1) := gOLD(i).SalutationTitle;
State_00.Salutations_ToInsert(State_00.Salutations_ToInsert.Count + 1) := gNEW(i).SalutationTitle;
END LOOP;
END AFTER STATEMENT;
END;
This results in error ORA-04091. I've also tried moving the select into the AFTER STATEMENT section which works, but there is no way to access the old values. If somebody has a solution for this it would be most appreciated.
EDIT:
I created a minimal reproducible example:
CREATE TABLE example_table (
id VARCHAR2(10),
name NVARCHAR2(100)
);
CREATE TABLE log_table (
id VARCHAR2(10),
new_name NVARCHAR2(100),
old_name NVARCHAR2(100)
);
CREATE OR REPLACE TRIGGER example_trigger
AFTER UPDATE ON example_table
FOR EACH ROW BEGIN
INSERT INTO log_table VALUES(:old.id, :new.name, :old.name);
END;
INSERT INTO example_table VALUES('01', 'Daniel');
-- this works as expected
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
CREATE TABLE example_table (
id VARCHAR2(10),
-- this is the problematic part
name NVARCHAR2(100) COLLATE XCZECH_PUNCTUATION_CI
);
INSERT INTO example_table VALUES('01', 'Daniel');
-- here nothing is inserted into log_example, if you try to
-- recompile the trigger you'll get error PLS-00049
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
DROP TABLE log_table;
DROP TRIGGER example_trigger;
In the discussion you reference a document concerning USING_NLS_COMP. That has nothing to do with the error you are getting. The error ORA-04091 is a reference to the table that fired the trigger (mutating). More to come on this. I am not saying you do not have USING_NLS_COMP issues, just that they are NOT causing the current error.
There are misconceptions shown in your trigger. Beginning with the name itself; you should avoid the prefix SYS. This prefix is used by Oracle for internal objects. While SYS prefix is not specifically prohibited at best it causes confusion. If this is actually created in the SYS schema then that in itself is a problem. Never use SYS schema for anything.
There is no reason to create a record type containing a single variable, then create a collection of that type, and finally define variables of the collection. Just create a collection to the variable directly, and define variables of the collection.
The bulk collect in the select statements is apparently misunderstood as used. I assume you want to collect all the new and old values in the collections. Bulk collect however will not do this. Each time bulk collect runs the collection used is cleared and repopulated. Result being the collection contains only the only the LAST population. Assuming id is unique the each collection would contain only 1 record. And now that brings us to the heart of the problem.
The error ORA-04091: <table name> is mutating, trigger/function may not see it results from attempting to SELECT from the table that fired the trigger; this is invalid. In this case the trigger fired due to a DML action on the persons table as a result you cannot select from persons in a row level trigger (stand alone or row level part of a compound trigger. But it is not needed. The pseudo rows :old and :new contain the complete image of the row. To get a value just reference the appropriate row and column name. Assign that to your collection.
Taking all into account we arrive at:
create or replace trigger personssalutation
for update
on persons
compound trigger
type persons_table is table of
persons.salutationtitle%type;
gold persons_table := persons_table();
gnew persons_table := persons_table();
before each row is
begin
gold.extend;
gold(gold.count) := :old.salutationtitle;
end before each row;
after each row is
begin
gnew.extend;
gold(gold.count) := :new.salutationtitle;
end after each row;
after statement is
begin
for i in 1 .. gnew.count loop
state_00.salutations_todelete(state_00.salutations_todelete.count + 1) := gold(i);
state_00.salutations_toinsert(state_00.salutations_toinsert.count + 1) := gnew(i);
end loop;
end after statement;
end personssalutation;
NOTE: Unfortunately you did not provide sample data, nor description of the functions in the AFTER STATEMENT section. Therefore the above is not tested.

Trigger is not working when I'm trying to insert a value into a table

I am trying to make an Insert Trigger for a table called Marks which has id, id_student, id_course, value, data_notation, created_at, updated_at.
I need to make an Update on the old value, if the value I want to insert is higher than the one already exists in the column, and if there are no values in the column you would do an Insert with the new value.
I created the Trigger and there are no compilation errors.
CREATE OR REPLACE TRIGGER insert_value
before INSERT ON Marks
FOR EACH ROW
BEGIN
IF (:OLD.value IS NULL) THEN
DBMS_OUTPUT.PUT_LINE('Inserting.. because value is null');
UPDATE Marks SET value = :NEW.value where id_student = :NEW.id_student;
ELSE
DBMS_OUTPUT.PUT_LINE('Updating old value.. if old value is smaller than the one we want');
IF (:OLD.value < :NEW.value) THEN
UPDATE Marks SET value = :NEW.value where :OLD.id_student = :NEW.id_student;
END IF;
END IF;
END;
I want to change the old value from an existing value 5 to null for a specific id.
update Marks set value = null where id = 692;
select * from Marks where id = 692;
But when I'm trying to insert a value into the table so I can change the value null into 6 via the trigger
INSERT INTO Marks
VALUES (692, 43, 12, 6, '13-02-2018', '13-02-2018', '13-02-2018');
I am receiving an error.
Error report -
SQL Error: ORA-00001: unique constraint (STUDENT.SYS_C007784) violated
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
And it prints one time:
Inserting.. because value is null
But when I'm trying to check if the trigger did its job, using:
SELECT * from Marks where id = 692;
It doesn't update anything.
It has to be a trigger triggered by an insert operation. So I can't make the insert into the table, but how else should I write it so it works?
You problem comes from recursive calling the trigger due to the insert. The following would work. It does not catch update statements. It only cares for inserts. If the row exists already the row gets deleted first and the existing value is used for the insert if the existing value is higher.
set lin 20000
drop table marks;
create table Marks(
id number,
id_student number,
id_course number,
value number,
data_notation varchar2(40),
created_at timestamp,
updated_at timestamp,
CONSTRAINT marks#u UNIQUE (id, id_student, id_course)
);
create or replace trigger mark_trigger
before insert on marks
for each row
declare
l_value number;
l_data_notation varchar2(40);
l_created_at timestamp;
begin
select value, data_notation, created_at
into l_value, l_data_notation, l_created_at
from
(select *
from marks
where marks.id = :new.id
and marks.id_student = :new.id_student
and marks.id_course = :new.id_course
order by created_at desc)
where rownum=1;
if l_value is null then
return;
end if;
if l_value > :new.value then
:new.value := l_value;
:new.data_notation := l_data_notation;
:new.created_at := l_created_at;
else
:new.updated_at := systimestamp;
end if;
delete from marks
where marks.id = :new.id
and id_student = :new.id_student
and id_course = :new.id_course;
exception
when no_data_found then
null;
end;
create or replace procedure marks_insert(
i_id number,
i_id_student number,
i_id_course number,
i_value number,
i_data_notation varchar2
)
is
begin
INSERT INTO marks
VALUES (i_id, i_id_student, i_id_course, i_value, i_data_notation, systimestamp, null);
END marks_insert;
begin
delete from marks;
marks_insert(1,1,1,5,'1 first entry');
marks_insert(1,1,1,6,'1 second entry');
marks_insert(1,1,2,3,'2 first entry');
marks_insert(1,1,2,2,'2 second entry');
end;
select * from marks;
Output:
Table dropped.
Table created.
Trigger created.
Procedure created.
PL/SQL procedure successfully completed.
ID ID_STUDENT ID_COURSE VALUE DATA_NOTATION CREATED_AT UPDATED_AT
---------- ---------- ---------- ---------- ---------------------------------------- -------------------------------------------------- --------------------------------------------------
1 1 1 6 1 second entry 07/05/2019 13:31:31.266817 07/05/2019 13:31:31.266928
1 1 2 3 2 first entry 07/05/2019 13:31:31.268032
2 rows selected.
You are inserting into the Marks when you insert into the Marks (the insert statement in the trigger before inserting) and so on in a recursive way. Hence the direct cause of error.

How to fix mutating table error in trigger?

I have two tables with the name of item and stock_item.
When I update the item table then trigger with the name beforeItem should fire, which subtracts new updated qty from stock_qty. But it throws
ORA-04091: table **** is mutating trigger/function may not see it
How can I fix this?
My tables:
create table stock_item
(no number primary key,itemName varchar2(10),stock_Qty number);
create table item
(no number,Name varchar2(10),qty number);
My trigger:
create or replace trigger beforeItem
before update on item
for each row
declare
chk_no number;
chk_item varchar2(10);
chk_qty number;
--pragma AUTONOMOUS_TRANSACTION;
-- this code will skip the update code.
begin
select no,name,qty into chk_no, chk_item,chk_qty from item where no=:new.no
and name=:new.name;
update stock_item set itemName = itemName - chk_qty where no=chk_no and
itemName=chk_item;
--commit;
end;
Oracle hurls ORA-04091 when a trigger issues DML against the table which owns the trigger; this includes SELECT statements. The reason is simple: the state of the table is unknown because the trigger is firing during a transaction, so the outcome of the trigger's DML is unpredictable.
The solution is usually quite simple: remove the DML. That certainly would seem to be the answer here, because your :NEW record has all the values you need to execute the update on stock_item:
create or replace trigger beforeItem
before update on item
for each row
begin
update stock_item si
set si.stock_Qty = si.stock_Qty - :new.qty
where si.no = :new.no;
end;
but the stock_item table don't know what is the current value of the item table qty to subtract from.
Okay, so what you mean is, you want to update the STOCK_ITEM.QTY with the difference between the old (current) ITEM.QTY and the new (updated) value. Then that would be something like:
create or replace trigger beforeItem
before update on item
for each row
begin
update stock_item si
set si.stock_Qty = si.stock_Qty - ( (nvl(:old.qty,0) - nvl(:new.qty,0)) )
where si.no = :new.no;
end;
Here is a demo of my solution on SQL Fiddle.
Incidentally, note that I have corrected your UPDATE statement: subtracting Item Quantity from the Stock Name really doesn't make sense. Also, there is no need to use itemName in the WHERE clause when there is a primary key to use.
You cannot reference table ITEM in this trigger because it causes your error. Instead of using SELECT statement use new/old parameters. Try this version of the trigger.
create or replace trigger beforeItem
before update on item
for each row
begin
-- if :new.qty is not null then
update stock_item set
-- logic to maintaint if no changes on qty field were done
stock_Qty = stock_Qty - ( nvl(:new.qty,0) - nvl(:old.qty,0) )
where no=:new.no and itemName=:new.name;
-- end if;
end;

How to get exact updated column name in oracle trigger?

I'm going to create a trigger to audit a table. Suppose my table define as below.
COUNTRY_ID NOT NULL CHAR(2)
COUNTRY_NAME VARCHAR2(40)
REGION_ID NUMBER
and my log table created as below.
create table country_log(
username varchar2(10),
transaction_date date,
new_value varchar(20),
old_value varchar(20)
)
My half completed trigger would be like below.
CREATE OR REPLACE TRIGGER tr_countryTable
AFTER UPDATE ON COUNTRIES
FOR EACH ROW
BEGIN
insert into country_log (username,transaction_date,new_value,old_value ) values (USER, sysdate,**:New, :Old** );
END;
/
I need to know instead of comparing each column value in :old and :new, how to get exactly updated column's new and old values.
In an UPDATE trigger, a column name can be specified with an UPDATING predicate to determine if the named column is being updated. Let's define a trigger as the following:
CREATE OR REPLACE TRIGGER tr_countryTable
AFTER UPDATE ON COUNTRIES
FOR EACH ROW
BEGIN
IF UPDATING ('COUNTRY_NAME') THEN
-- :new.COUNTRY_NAME is new value
-- :old.COUNTRY_NAME is old value
END IF;
IF UPDATING ('REGION_ID') THEN
-- :new.REGION_ID is new value
-- :old.REGION_ID is old value
END IF;
END;
/
Oracle 11g triggers documentation

How to Insert affected rows in a new table

Following is a dummy table on which i am performing some updations. Here, i am only manipulating the "NAME" column. What i need is to insert the affected row, (say) i change the second for for "ID"-2, then i need that only this row should get inserted in a new table with new / updated value. I have tried doing so via a trigger but it vomits an error regarding table mutation.
ID NAME
================
1 Vaz
2 Dan
3 Steve
The table i want the data to get inserted has the same structure as the above mentioned table and same columns with same datatype.
Please suggest if this could be done in any other way or I am writing wrong code for the trigger. Here is the code i wrote for the purpose :
CREATE OR REPLACE TRIGGER HR.ins_temp
after UPDATE OF name ON HR.TEMP2 FOR EACH ROW
BEGIN
INSERT INTO temp3
(SELECT NAME
FROM temp2
WHERE (:new.NAME<>:old.NAME));
END;
temp2 is the manipulation tabel and temp3 is the new one.
No need for a SELECT. And you probably also want to put the ID value into the table temp3
CREATE OR REPLACE TRIGGER HR.ins_temp
after UPDATE OF name ON HR.TEMP2 FOR EACH ROW
BEGIN
INSERT INTO temp3 (id, name)
values (:new.id, :new.name);
END;
/
And as the trigger is defined as update of name you don't really need to check if the name changed, but if you want, you can do:
CREATE OR REPLACE TRIGGER HR.ins_temp
after UPDATE OF name ON HR.TEMP2 FOR EACH ROW
BEGIN
if ( (:new.name <> :old.name)
OR (:new.name is null and :old.name is not null)
OR (:new.name is not null and :old.name is null))
then
INSERT INTO temp3 (id, name)
values (:new.id, :new.name);
end if;
END;
/

Resources