I'm having issues with a trigger that keeps mutating. The goal of the trigger is when a file path is updated change part of the string. Here is what I have so far, it compiles just fine.
CREATE OR REPLACE TRIGGER TAU_ATTACHMENTS
AFTER UPDATE
ON ATTACHMENTS
FOR EACH ROW
BEGIN
IF :new.PATH LIKE 'file://Y:%' THEN
UPDATE ATTACHMENTS SET PATH = REPLACE(:new.PATH, 'file://Y:','file://\\MPS7536');
END IF;
END;
/
When I do a test of updating a single row I get the usual mutating table error. Any advice? I'd be extremely grateful!
You are obviously want to modify value of a column in trigger before storing it. But instead the provided code is trying to update whole attachments table. You can modify the value of record which is being updated like this:
CREATE OR REPLACE TRIGGER TAU_ATTACHMENTS
BEFORE UPDATE
ON ATTACHMENTS
FOR EACH ROW
BEGIN
IF :new.PATH LIKE 'file://Y:%' THEN
:new.PATH = REPLACE(:new.PATH, 'file://Y:','file://\\MPS7536');
END IF;
END;
/
Note that this should be BEFORE UPDATE trigger.
Related
I am trying to create a trigger function in Postgres 13 that, before an insert or update operation is done, will copy values from two columns to two other columns.
Before an update/insert operation my table test.run_conf looks like this:
hostname
run_config_current
run_config_current_hash
run_config_last
run_config_last_hash
switch01
old_txt_str
32314322
When an update/insert occurs I want the value from run_config_current copied to run_config_last and run_config_current_hash copied to run_config_last_hash. Then allow the update/insert operation to occur against column run_config_current - after which the trigger function will recalculate the value for run_config_current_hash.
For testing, I run the following query to insert new data into the column run_config_current:
INSERT INTO test.run_conf (hostname, run_config_current) VALUES ('switch01', 'new_txt_str' )
ON CONFLICT (hostname) DO UPDATE SET run_config_current = excluded.run_config_current
However what happens is the table test.run_conf gets updated as follows - the new value gets inserted into both columns run_config_current and run_config_last:
hostname
run_config_current
run_config_current_hash
run_config_last
run_config_last_hash
switch01
new_txt_str
47173234
new_txt_str
32314322
The trigger and function I have in place now that is not working properly is:
CREATE OR REPLACE FUNCTION test.run_conf_hash_gen_func()
RETURNS TRIGGER
SET SCHEMA 'test'
LANGUAGE plpgsql
AS $$
BEGIN
NEW.run_config_last := NEW.run_config_current;
NEW.run_config_last_hash := NEW.run_config_current_hash;
NEW.run_config_current_hash := MD5(NEW.run_config_current);
RETURN NEW;
END;
$$
CREATE TRIGGER run_conf_hash_gen_trig
BEFORE INSERT OR UPDATE on test.run_conf
FOR EACH ROW
EXECUTE PROCEDURE test.run_conf_hash_gen_func();
Please help me to understand why my code seems to be copying the new value for run_config_current into run_config_last. Thank you!
I figured this out. My issue was that I was referencing the NEW var as reference for the values to assign. Since the vars NEW and OLD are set at trigger execution, but before changes, my old code was using the values I sent via my INSERT/UPDATE statement as what to assign to my _last columns.
Here is my updated trigger function that works as expected. I commented out the old/bad lines and the new lines underneath are what is working:
CREATE OR REPLACE FUNCTION test.run_conf_hash_gen_func()
RETURNS TRIGGER
SET SCHEMA 'test'
LANGUAGE plpgsql
AS $$
BEGIN
-- NEW.run_config_last := NEW.run_config_current;
NEW.run_config_last := OLD.run_config_current;
-- NEW.run_config_last_hash := NEW.run_config_current_hash;
NEW.run_config_last_hash := OLD.run_config_current_hash;
NEW.run_config_current_hash := MD5(NEW.run_config_current);
RETURN NEW;
END;
$$
I have created a view which has columns that i need to track for any change and load it into a history table. I created a Trigger as below and when i execute change, it only updates the History but does not add a new updated record. Any idea what im doing wrong?
create or replace function asset_h_fn() returns trigger
LANGUAGE plpgsql
as $$
begin
if (asset = 'Insert') then
insert into asset_history (sys_period,col1,col2,col3,col4,col5,col6)
values (tstzrange(lower(OLD.sys_period), current_timestamp), OLD.col1, OLD.col2, OLD.col3, OLD.col4, OLD.col5);
NEW.sys_period = tstzrange(current_timestamp,null);
return new;
return old;
end if;
end $$ ;
The RETURN OLD; in your code is fortunately unreachable; remove it.
You don't show us the CREATE TRIGGER statement, but it must be an INSTEAD OF trigger.
A view does not hold any data, it is an SQL statement with a name. So if you want a new row to appear in the view, you have to add a second INSERT to the trigger function that inserts a row into the table(s) on which the view is defined.
Well, It depends on how do you define your trigger...
Looking at your code I suppose you should use CREATE TRIGGER <trigger_name> BEFORE INSERT OR UPDATE ON <table_name> FOR EACH ROW EXECUTE PROCEDURE asset_h_fn();
The key here is BEFORE INSERT - if you wish to alter somehow inserted into original table data... If you don't wish to alter it, you should probably use AFTER INSERT.
You may read more about defining triggers and see some examples in official docs
My trigger is defined the following way:
CREATE TRIGGER update_contract_finished_at
AFTER INSERT OR DELETE OR UPDATE OF performed_on
ON task
FOR EACH ROW
EXECUTE PROCEDURE update_contract_finished_at_function();
I now want to evoke this trigger to set the variables which are updated by the trigger. How do I do that?
Something like
for each row in task
execute procedure update_contract_finished_at_function();
I know I can update with a standard update set statement. I also want to verifiy that my trigger works on all the data correctly.
I'd write a slightly modified copy of update_contract_finished_at_function that takes type task as input and returns void.
Then replace NEW in the trigger function with $1 and call the function like this:
SELECT copy_func(task) FROM task;
If the functions are almost identical, it should be good enough to test the trigget function.
The way to manually trigger your on update trigger once would be:
UPDATE task SET performed_on = performed_on
however depending on how complicated your logic is in there and how many rows you have in the table a separate query might be significantly faster for initializing a large number of rows.
Since you mentioned you want to test the behaviour of your trigger you can clone the table or do a table or database dump and restore the data afterwards. If this is a live system you should instead do a database dump, restore to another system, add your trigger, test it, repeat from restore until you nail it... and only after you're sure it does what you want update the live system with it.
I ended up writing a PL/pgSQL function that in a loop processes all events in chronological order and calling it:
create or replace function process_event_history()
returns void
language plpgsql
as
$$
declare
event record;
begin
for event in
select id, timestamp
from events
order by timestamp
loop
update events set timestamp = event.timestamp
where id = event.id;
end loop;
end;
$$;
--;;
-- Execute the above function causing the trigger to run for all events.
select process_event_history();
--;;
-- Remove the temporary processing function.
drop function process_event_history();
I use postgresql 11.
I think my problem comes from a trigger that doesn't do an update so the next trigger doesn't launch.
I have a table projet with columns : projet_temps_doe, projet_temps_etudes, projet_temps_globale.
The goal is to update each columns depending on other columns values.
The idea is : projet_temps_globale = projet_temps_doe + projet_temps_etudes.
I have a first trigger on projet_temps_doe which works perfectly:
create function temps_globale_doe() returns trigger
language plpgsql
as
$$
begin
new.projet_temps_globale_doe := new.projet_temps_doe_gc_bts + new.projet_temps_doe_gc_nra;
return new;
end;
$$;
CREATE TRIGGER temps_globale_doe
BEFORE UPDATE OF projet_temps_doe_gc_bts, projet_temps_doe_gc_nra
ON public.projet
FOR EACH ROW
EXECUTE PROCEDURE public.temps_globale_doe();
I have a similar trigger on projet_temps_etudes which works perfectly too.
Then the trigger I struggle with on projet_temps_globale :
create trigger maj_temps_globale_projet
before update of projet_temps_doe, projet_temps_etudes on projet
for each row
execute procedure maj_temps_globale_projet();
create or replace function maj_temps_globale_projet()returns trigger
language plpgsql
as
$$
begin
new.projet_temps_globale := new.projet_temps_doe + new.projet_temps_etudes;
raise info 'TEST!!';
return new;
end;
$$;
When projet_temps_doe and/or projet_temps_etudes are updated via triggers my last trigger doesn't launch. However when I manually change projet_temps_doe and/or projet_temps_etudes values the trigger maj_temps_globale_projet is fired.
I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.
The doc says
The trigger will only fire if at least one of the listed columns is
mentioned as a target of the UPDATE command.
The column projet_temps_globale_doe is not part of the update command but is rather set in another trigger via new.projet_temps_globale_doe = ... so the trigger on this particular column is not called.
It would be easier to have only one trigger on the entire table that sets the 3 derived values.
In Postgresql I can have two kinds of triggers: FOR EACH ROW and FOR EACH STATEMENT. If I do a FOR EACH ROW trigger, I can add a WHERE clause something like OLD.* != NEW.* so it only fires if something has actually changed. Is there any way to do something similar with STATEMENT level triggers? I know I can't do the same thing since OLD and NEW aren't available, but I was thinking perhaps there might be a way to check the number of rows changed from within my function itself or the like.
Usage case: I am using the postgresql NOTIFY system to notify my app when data changes. Ideally, the app would get a single notification each time one or more records changes, and not get notified at all if data stays the same (even if an UPDATE was run). With a basic AFTER UPDATE FOR EACH STATEMENT trigger, I am getting notified every time an update statement runs - even if it doesn't actually change anything.
You should create two triggers: before update for each row and after update for each statement.
The first trigger checks if the table is being updated and sets a flag if so.
The second trigger checks the flag and performs notify if it was set.
You can use a custom configuration parameter as the flag (e.g. flags.the_table).
The solution is simple and safe, as the parameter is local in the current session.
create or replace function before_each_row_on_the_table()
returns trigger language plpgsql
as $$
begin
if new <> old then
set flags.the_table to 'on';
end if;
return new;
end $$;
create or replace function after_each_statement_on_the_table()
returns trigger language plpgsql
as $$
begin
if current_setting('flags.the_table', true) = 'on' then
notify your_channel, 'the_table was updated';
set flags.the_table to 'off';
end if;
return null;
end $$;
create trigger before_each_row_on_the_table
before update on the_table
for each row execute procedure before_each_row_on_the_table();
create trigger after_each_statement_on_the_table
after update on the_table
for each statement execute procedure after_each_statement_on_the_table();
The function current_setting() with two arguments is available in Postgres 9.6 or later.