Update table with a trigger - How to solve mutation error - oracle

I'm trying to create a trigger for updating some columns of a record after creating or updating the record.
There my code:
CREATE OR REPLACE TRIGGER "TRIGGER_UPDATE_CONTRACTOR_LOT"
AFTER INSERT OR UPDATE ON "CONTRACTOR_LOT"
FOR EACH ROW
DECLARE
CONTRACT_LOT_LABEL VARCHAR2(255 BYTE);
CONTRACTOR_LABEL VARCHAR2(200 BYTE);
BEGIN
SELECT LABEL INTO CONTRACT_LOT_LABEL FROM LOT_T WHERE ID = :NEW.LOT_ID;
SELECT CONTRACTOR INTO CONTRACTOR_LABEL FROM CONTRACTOR_T WHERE ID = :NEW.CONTRACTOR_ID;
UPDATE CONTRACTOR_LOT
SET LABEL = CONTRACT_LOT_LABEL || ':' || CONTRACTOR_LABEL,
FRAMEWORK_CONTRACT_NUMBER_LABEL = :NEW.ORDER || ':' || CONTRACT_LOT_LABEL || :NEW.FRAMEWORK_CONTRACT_NUMBER
WHERE ID = :NEW.ID;
END;
I get an error ORA-04091 (mutation)
I tried to add PRAGMA AUTONOMOUS_TRANSACTION; and I get the error ORA-00060 (deadlock detected while waiting for resource)
So I add COMMIT; after the update, but it's still the same issue.
Could you help me please with that?

You can't perform DML on the parent table within a trigger, and there's really rarely any reason to do so. Try this, with a BEFORE trigger and just modifying the NEW values of the existing INSERT or UPDATE that is firing the trigger:
CREATE OR REPLACE TRIGGER TRIGGER_UPDATE_CONTRACTOR_LOT
BEFORE INSERT OR UPDATE ON CONTRACTOR_LOT
FOR EACH ROW
DECLARE
CONTRACT_LOT_LABEL VARCHAR2(255 BYTE);
CONTRACTOR_LABEL VARCHAR2(200 BYTE);
BEGIN
SELECT LABEL INTO CONTRACT_LOT_LABEL FROM LOT_T WHERE ID = :NEW.LOT_ID;
SELECT CONTRACTOR INTO CONTRACTOR_LABEL FROM CONTRACTOR_T WHERE ID = :NEW.CONTRACTOR_ID;
:NEW.CONTRACTOR_LOT := CONTRACT_LOT_LABEL || ':' || CONTRACTOR_LABEL;
:NEW.FRAMEWORK_CONTRACT_NUMBER_LABEL := :NEW.ORDER || ':' || CONTRACT_LOT_LABEL || :NEW.FRAMEWORK_CONTRACT_NUMBER;
END;

Your trigger is catching changes and inserts on this table and then update on the same table. It seems to be vicious circle. Because update inside the trigger also will want to initiate this trigger

Related

Update of column value within a trigger

Before insert or update of any columns I want to update 1 system column with standard hash MD5 of all table columns, trigger is attached to. My intention is not to tailor this trigger with enumeration of all columns for each trigger and have a function that returns concatenated list of columns per table.
Table DDL:
create table TEST (
id int,
test varchar(100),
"_HASH" varchar(32)
);
Here is my trigger DDL that I would love to work :
CREATE TRIGGER TEST_SYS_HASH_BEFORE_INSERT_OR_UPDATE
BEFORE INSERT OR UPDATE
ON TEST
FOR EACH ROW
DECLARE
var_columns VARCHAR2(10000);
BEGIN
var_columns := FUNC_LISTAGG_EXT(‘TEST');
EXECUTE IMMEDIATE 'SELECT STANDARD_HASH(' || var_columns || ', ''MD5'') from dual'
INTO :new."_HASH";
END;
However this is simply taking headers and set same hash for every row. If I should do this manually , trigger would look like this, what works as I desire, but create it for several tens of tables would be overwhelming
CREATE OR REPLACE TRIGGER TEST_SYS_HASH_BEFORE_INSERT_OR_UPDATE
BEFORE INSERT OR UPDATE
ON TEST
FOR EACH ROW
DECLARE
var_columns VARCHAR(10000);
BEGIN
var_columns := FUNC_LISTAGG_EXT('TEST');
SELECT STANDARD_HASH( :new."ID" || :new."TEST" , 'MD5' )
INTO :new."_HASH";
FROM DUAL;
END;
So my question is whether solution is achievable
Note:
FUNC_LISTAGG_EXT function returns concatenated list of columns from system view

How to prevent trigger from updating the timestamp on a column in Oracle?

I have a table which has a column on which I have set a trigger to update the timestamp when there is an update on the row
CREATE TABLE mytable (
id NUMBER NOT NULL,
sid VARCHAR(10) NOT NULL,
stext VARCHAR(10),
tid VARCHAR(10),
ttext VARCHAR(10),
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique PRIMARY KEY (id) ENABLE
);
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
BEGIN
select systimestamp into :new.last_updated from dual;
END;
/
However, when certain SQLs are executed on the table, I do NOT want the 'last_updated' column to be updated. For instance, when the below SQL updates this table, I do NOT want the trigger to kick in. I need the column to retain the last_updated value so that my other queries do not get messed up. However, when other SQLs do updates on the data, I want the column to be updated.
Any ideas on how this could be done?
MERGE INTO mytable
USING dual
ON (id = ?)
WHEN MATCHED THEN
UPDATE SET
stext = case when sid = ? then ? else stext end,
ttext = case when tid = ? then ? else ttext end
You can easily keep a supplied value and only insert the current time if it is not supplied:
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
BEGIN
:new.last_updated := coalesce(:new.last_updated, systimestamp);
END;
If you do not want to fire trigger when you update STEXT or TTEXT columns please try this:
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
BEGIN
if (not updating('stext') and not updating('ttext')) then
:new.last_updated := systimestamp;
end if;
END;
Thanks
You can put that specific SQL Statement into a PROCEDURE in which the module info is updated for the concerned application(always call this statement from this procedure only) such as
CREATE OR REPLACE PROCEDURE Merge_Text AS
BEGIN
DBMS_APPLICATION_INFO.SET_MODULE($$plsql_unit,null);
MERGE INTO mytable
USING dual
ON (id = ?)
WHEN MATCHED THEN
UPDATE SET
stext = case when sid = ? then ? else stext end,
ttext = case when tid = ? then ? else ttext end;
END;
/
and then distinguish this caller module within the trigger such as
CREATE OR REPLACE trigger last_updated_trigger
BEFORE UPDATE ON mytable
FOR EACH ROW
v_module VARCHAR2(80);
v_action VARCHAR2(80);
BEGIN
DBMS_APPLICATION_INFO.READ_MODULE(v_module, v_action);
IF v_module != 'MERGE_TEXT' THEN
:new.last_updated := systimestamp;
END IF;
END;
/

How to update a XML node, using a trigger in Oracle (XMLELEMENT)

I just start to write a trigger in oracle. And this trigger need to modify a node into a xml using the xmlements in Orecle
This is the trigger:
CREATE OR REPLACE TRIGGER trg_news_name
after insert on SelectiveProcess
for each ROW
declare
v_new_name VARCHAR2(400);
v_data xmltype;
BEGIN
v_data := :new.dataNode;
SELECT to_char(ExtractValue(v_data ,'/DATAS/NEWS_NAME'))
INTO
v_new_name
FROM DUAL;
v_new_name := :new.codigo || ' - ' || v_new_name ;
[.........]
end;
Now I need to update the node NEWS_NAME using the variable v_new_name.
I know I can update a node using a simple query like this follow exemple:
UPDATE SelectiveProcess SET dataNode =
UPDATEXML(dataNode,
'/DATAS/NEWS_NAME','TESTE NAME')
WHERE ID = 3;
But how to do that in a trigger?
AFTER INSERT Triggers do not allow you to update the :NEW values.
So, basically convert it to a BEFORE INSERT and override the :NEW value as you do it in an update statement.
select UPDATEXML(dataNode,
'/DATAS/NEWS_NAME', v_new_name ) INTO :new.dataNode FROM DUAL;
Or putting it all together.
SELECT UPDATEXML(dataNode,
'/DATAS/NEWS_NAME', :new.codigo || ' - '
to_char(ExtractValue(:new.dataNode ,'/DATAS/NEWS_NAME') ) ) INTO :new.dataNode FROM DUAL;

How to use Trigger with joining 2 tables?

I have 2 tables one is Schedule_table :
SID NOT NULL NUMBER(38)
FNUMBER VARCHAR2(20)
DEPARTURE_TIME TIMESTAMP(6) WITH TIME ZONE
ARRIVAL_TIME TIMESTAMP(6) WITH TIME ZONE
PRICE NUMBER
The second table is Flight_table
FNUMBER NOT NULL VARCHAR2(20)
DEPARTURE_APCODE CHAR(3)
ARRIVAL_APCODE CHAR(3)
Fnumber is PK in Flight_table and FK in Schedule_table.
I want the trigger to fire when the PRICE gets changed in Schedule_table
Also, to print a message of (Fnumber, ARRIVAL_APCODE, DEPARTURE_TIME ,ARRIVAL_APCODE).
I wrote this Code, it did not work.
create or replace trigger schedule_trigger after update on schedule
for each row when (new. price <> old. price)
begin
dbms_output.put_line( 'the flight number ' || :new.fnumber|| DEPARTURE_APCODE ||DEPARTURE_TIME|| ' has changed to '||:new.price ||' From'||:old.price);
end;
You are referring to the columns of Flight table - DEPARTURE_APCODE and DEPARTURE_TIME directly inside trigger which is not possible.You need to use a select
to fetch those values.
CREATE OR REPLACE TRIGGER schedule_trigger AFTER
UPDATE ON schedule
FOR EACH ROW
WHEN ( new.price <> old.price )
DECLARE
v_departure_apcode flight.departure_apcode%TYPE;
v_arrival_apcode flight.arrival_apcode%TYPE;
BEGIN
SELECT
departure_apcode,
arrival_apcode
INTO
v_departure_apcode,v_arrival_apcode
FROM
flight
WHERE
fnumber =:new.fnumber;
dbms_output.put_line('The flight number '
||:new.fnumber
|| v_departure_apcode
|| v_arrival_apcode
|| ' has changed to '
||:new.price
|| ' From '
||:old.price);
END;
/
And note that using DBMS_OUTPUT.PUT_LINE() inside a trigger is of no use as SQL developer sometimes does not display your output. Better create a log table and insert the records into it in your trigger.
You may require a commit statement for the output to be displayed.
SET SERVEROUTPUT ON;
UPDATE Schedule SET PRICE = 2502.00 where fnumber = 'F2004' ;
commit;
Commit complete.
The flight number F2004AB CD has changed to 2502 From 2501

Update the record which is being inserted/updated using a trigger

How can I update a full name as the combination of the first name and last name of the same record which is being updated/inserted using a trigger?
CREATE OR REPLACE TRIGGER updateFullName
BEFORE INSERT OR UPDATE ON table
FOR EACH ROW
BEGIN
:NEW.full_name := :NEW.first_name || ' ' || :NEW.last_name;
END;
/
Though a view would probably be more appropriate in this case

Resources