PostgreSQL Trigger and rows updated - postgresql

I am trying to update a table according to this trigger:
CREATE TRIGGER alert
AFTER UPDATE ON cars
FOR EACH ROW
EXECUTE PROCEDURE update_cars();
Trigger Function :
CREATE FUNCTION update_cars()
RETURNS 'TRIGGER'
AS $BODY$
BEGIN
IF (TG_OP = 'UPDATE') THEN
UPDATE hello_cars SET status = new.status
WHERE OLD.ID = NEW.ID;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
The trigger works fine. When the cars table is updated, the hello_cars table is updated but the status column in each row is updated and contains same new status! It must be updated according to a car ID.
I think my problem is in condition: WHERE OLD.ID = NEW.ID; but I can't tell what's wrong.
Thanks in advance.

OLD and NEW are aliases to the rows which fired the trigger. So when you execute a statement like
UPDATE cars SET status='xyz' WHERE cars.id = 42;
then the trigger function will execute
UPDATE hello_cars SET status='xyz' WHERE 42 = 42
The part 42=42 is always true. So each row in hello_cars is updated.
You really want something like
[...]WHERE hello_cars.id = OLD.ID
or a little shorter
[...]WHERE id = OLD.ID
But you also need to think about what happens, if the initial update changes cars.id. In this case OLD.ID is not equal NEW.ID. What should happen in the table hello_cars in this case? But that's another question.

OLD.ID and NEW.ID are referencing values in the updated row of the table cars and thus (unless you change the ID in cars) will always evaluate to true and therefor all rows in hello_cars are updated.
I think you probably want:
UPDATE hello_cars
SET status = new.status
WHERE id = new.id;
This assumes that there is a column id in the table hello_cars that matches the id in cars.

Related

Bounding box not working well in trigger postgresql - postgis

My Trigger function consists of collecting the vertices of a polyline table "depart_bt", searching for the point entities of the table “support_bt” which coincide with the vertices of the depart_bt table, then retrieving the value of the "code_dep" attribute (unique identifier of the depart_bt table) of each entity found and report it in the "code_dep" attribute (foreign key in the support_bt table).
CREATE OR REPLACE FUNCTION vertex_troncon() RETURNS TRIGGER
language plpgsql AS
$$
DECLARE
box_pt geometry(point);
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
IF NEW.code_dep IS NOT NULL THEN
UPDATE support_bt SET code_dep = NEW.code_dep WHERE support_bt.geom_point IN (SELECT (ST_DumpPoints(NEW.geom_line)).geom);
RETURN NULL;
END IF;
ELSE
RAISE WARNING 'Other action occurred: %, at %', TG_OP, now();
RETURN NULL;
END IF;
END;
$$
;
-- creation du trigger
DROP TRIGGER IF EXISTS vertex_dep ON depart_bt;
CREATE TRIGGER vertex_dep AFTER INSERT OR UPDATE ON depart_bt FOR EACH ROW EXECUTE PROCEDURE vertex_troncon();
My first code works well, but it has the disadvantage of iterating through all points of "support_bt" which may affect the performance of the trigger. So, I added a bounding box to limit the search area. However, I noticed that the bounding box does not work fine and all points in the support_bt table are selected.
/*UPDATE support_bt SET code_dep = NEW.code_dep WHERE support_bt.geom_point IN (SELECT (ST_DumpPoints(NEW.geom_line)).geom);*/
SELECT geom_point INTO box_pt FROM support_bt WHERE ST_contains((SELECT ST_SetSRID(ST_Extent(NEW.geom_line),32632)), support_bt.geom_point);
UPDATE support_bt SET code_dep = NEW.code_dep WHERE box_pt IN (SELECT (ST_DumpPoints(NEW.geom_line)).geom);
Thank you in advance for your help.

PostgreSQL trigger: first condition executes but not the second

A trigger works on the first part of a function but not the second.
I'm trying to set up a trigger that does two things:
Update a field - geom - whenever the fields lat or lon are updated, using those two fields.
Update a field - country - from the geom field by referencing another table.
I've tried different syntaxes of using NEW, OLD, BEFORE and AFTER conditions, but whatever I do, I can only get the first part to work.
Here's the code:
CREATE OR REPLACE FUNCTION update_geometries()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
update schema.table a set geom = st_setsrid(st_point(a.lon, a.lat), 4326);
update schema.table a set country = b.name
from reference.admin_layers_0 b where st_intersects(a.geom,b.geom)
and a.pk = new.pk;
RETURN NEW;
END;
$$;
CREATE TRIGGER
geom_update
AFTER INSERT OR UPDATE of lat,lon on
schema.table
FOR EACH STATEMENT EXECUTE PROCEDURE update_geometries();
There is no new on a statement level trigger. (well, there is, but it is always Null)
You can either keep the statement level and update the entire a table, i.e. remove the and a.pk = new.pk, or, if only part of the rows are updated, change the trigger for a row-level trigger and only update the affected rows
CREATE OR REPLACE FUNCTION update_geometries()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.geom = st_setsrid(st_point(NEW.lon, NEW.lat), 4326);
SELECT b.name
INTO NEW.country
FROM reference.admin_layers_0 b
WHERE st_intersects(NEW.geom,b.geom);
RETURN NEW;
END;
$$;
CREATE TRIGGER
geom_update
BEFORE INSERT OR UPDATE of lat,lon on
schema.table
FOR EACH ROW EXECUTE PROCEDURE update_geometries();

Trigger using adjusted OLD / NEW values

I am trying to put together trigger which does multiple things:
before any insert updates automatically columns created_date and created_by for the inserted row and also inserts this row with updated values into archive (history) table
before any update updates automatically values edited_date and edited_by for the updated row and inserts this updated row (including updated values edited_date and edited_by) into archive table
before any delete...
How to write efficiently the before (or after) update trigger using adjusted NEW (or OLD) values? Here is my trigger:
create trigger my_trigger
before insert or update or delete on my_table
for each row execute procedure my_function();
And function where I would like to use updated new row:
create function my_function() returns trigger as $$
begin
if (tg_op = 'INSERT') then
return 'something done here';
elsif (tg_op = 'UPDATE') then
update NEW set edited_date = now(), edited_by = current_user;
insert into my_table_hist select NEW.*;
elsif (tg_op = 'DELETE') then
return 'something done here';
end if;
return null;
end;
$$ language plpgsql;
What I mean by this is before update I update NEW row columns to current time and current user, then the table gets updated to this new row with updated values, and finally this row with updated values is inserted also into archive (history) table. But it clearly doesn't work like that, so how should I rewrite this trigger function to make it work?
There is much amiss here:
trigger functions must return a row, typically (a modified) NEW, or NULL to abort the operation
you do not update NEW since it is not a table, but you assign to its columns, like
NEW.created := current_timestamp;
for BEFORE DELETE triggers NEW is NULL, since there is no new version of the row

How to update same line than insert/update in plpgsql without reaching max_stack_depth

My problem is I reach the limit of the stack. And the message error says “You should increase max_stack_depth” and shows me the line that I use to update another column.
I encounter this error after an update request (code below).
I know my problem may look like others questions but none of them explain why I reach this error.
What I want to do is simple and I've done it many times, but here I'm missing something.
I want: if there is an update on the table support_fh pull a trigger. I expect this trigger to do:
if the new values of the update request are section= 'DISTRIBUTION' and modulo= '6' and fabricant = 'NEXANS' and capacite = 12 then set diametre = '12.5' (code below).
Of course it is the line of diametre from the same line than update request.
Futhermore I know I should use the character varying type instead of the integer type, but I was asked to so it like that.
My trigger function:
create or replace function maj_diam() returns trigger
as
$$
Declare fab_loc character varying;
Declare section_loc character varying;
Declare capa_loc character varying;
Declare modulo_loc character varying;
BEGIN
Select fabricant into fab_loc from support_fh where id = new.id;
Select section into section_loc from support_fh where id = new.id;
Select capcite into capa_loc from support_fh where id = new.id;
Select modulo into modulo_loc from support_fh where id = new.id;
if fab_loc = 'NEXANS' and section_loc = 'DISTRIBUTION'
and capa_loc = '12' and modulo_loc = '6' then
update support_fh set diametre = '12.2' where id = new.id;
endif;
return new;
end;
$$;
My trigger :
create trigger maj_diam
After update on support_fh
for each row
execute procedure maj_diam();
My update request to test my trigger :
update support_fh set fabricant = 'NEXANS', section = 'DISTRIBUTION', capacite = '12', modulo = '6'
where id = 11827;
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.
You get that problem because the update in the trigger will launch the trigger again, causing an infinite loop. No value of max_stack_depth is big enough for that (and increasing that value too much is dangerous anyway).
Instead of what you are doing, you should create a BEFORE trigger and modify the NEW value that are about to be inserted:
IF NEW.fab_loc = 'NEXANS' AND NEW.section_loc = 'DISTRIBUTION'
AND NEW.capa_loc = '12' AND NEW.modulo_loc = '6'
THEN
NEW.diametre := '12.2';
END IF;
If you want to change columns in a row that is updated (or inserted), don't use UPDATE in the trigger function. Declare the trigger as BEFORE UPDATE, then simply assign the new values.
You also don't need four select statements to read four columns from the same table.
But as you are only accessing columns from the same row that was updated, you don't even need a SELECT at all.
So your trigger function can be simplified to:
create or replace function maj_diam() returns trigger
as
$$
BEGIN
if new.fabricant = 'NEXANS'
and new.section = 'DISTRIBUTION'
and new.capcite = '12'
and new.modulo = '6'
then
new.diametre := '12.2';
end if;
return new;
end;
$$;
Assuming that capcite, modulo and diametre are actually numbers, you shouldn't compare them with varchar values. So the above code should probably be: new.diametre := 12.2; or new.capcite = 12.
And the trigger definition needs to be changed to:
create trigger maj_diam
BEFORE update on support_fh
for each row
execute procedure maj_diam();

How to modify Trigger to update a single attribute in PostgreSQL

Here is my sample table.
CREATE TABLE employee_test(
idTst SERIAL PRIMARY KEY,
monthDownload VARCHAR(6),
changeDate DATE);
I am trying to create a function and trigger that would update changeDate attribute with a current date when monthDownload attribute is updated.
The function I have it works with one problem. It updates all records instead of the one that was updated.
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where OLD.idTst = NEW.idTst;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
Trigger
Create TRIGGER dataTest
AFTER UPDATE
ON employee_test
FOR EACH ROW
EXECUTE PROCEDURE downloadMonthChange();
When I execute the following Update statement:
UPDATE employee_test SET monthDownload = 'oct12'
WHERE idTst = 1;
All changeDate rows get update with a current date.
Is there a way to have only a row with changed record to have a current date updated.
If you use a before trigger you can write directly to NEW
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
NEW.changeDate = current_date;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
the other option when you must use an after trigger is to include the primary key in the where clause. It appears that you were trying to do this, but you had a spurious OLD in the query. beause of that the where clause was only looking at the record responsible for the trigger call, and not limiting which records were to be updated.
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where idTst = NEW.idTst;