I have this trigger
procedure CASE_A
as
UPDATE table1 SET field1 = (SELECT bus FROM view1 WHERE table1.document = view1.document)
end;
the below trigger is supposed to execute the above procedure CASE_A (Case_A is supposed to put a value into field1)
CREATE OR REPLACE TRIGGER "CASE_A" AFTER INSERT ON "TABLE1" FOR EACH ROW
BEGIN
CASE_A;
END;
but, it's not doing that.
you are doing it wrong, at least assuming this is Oracle.
better do it like this:
CREATE OR REPLACE TRIGGER "CASE_A" BEFORE INSERT ON "TABLE1" FOR EACH ROW
BEGIN
SELECT bus INTO :new.field1 FROM view1 WHERE :new.document = view1.document;
END;
EDIT: since view1 depends on table1, we need to do a bit more... (read comments for more information)
CREATE OR REPLACE TRIGGER "CASE_A" BEFORE INSERT ON "TABLE1" FOR EACH ROW
BEGIN
SELECT sum(FUNCTION_CASEA(b.DUE, b.RETD)) INTO :new.field1
FROM table2
WHERE table1.document = table2.document
AND table1.field1 = table2.branch;
END;
Related
I came across a strange behavior (at least for me) with PostgreSQL and a trigger BEFORE UPDATE.
I have a table witch has an updated_at column witch is set by a BEFORE UPDATE trigger.
I need to add new columns to this table and set their values with an UPDATE query (not with DEFAULT).
It works just fine excepts when i do an UPDATE juste before adding those columns.
Here's an example :
ALTER TABLE my_schema.my_table ADD COLUMN new_column varchar(50);
UPDATE my_schema.my_table SET new_column = 'new_column_update' WHERE id = xxxxxx;
This script works fine.
But if i do an UPDATE before :
UPDATE my_schema.my_table SET other_column = 'other_column_update' WHERE id = xxxxxx; -- the TRIGGER is triggered
ALTER TABLE my_schema.my_table ADD COLUMN new_column varchar(50);
UPDATE my_schema.my_table SET new_column = 'new_column_update' WHERE id = xxxxxx; -- this UPDATE does't do anything
It doesn't works anymore.
After a few (a lot) hours, i found that the trigger BEFORE UPDATE is reponsible. But i can't find why.
I found a workaround by temporary disabling the trigger
ALTER TABLE my_table DISABLE TRIGGER update_date;
Here is a dbfiddle, just run it to see this behaviour :
dbfiddle
Here is the code in dbfiddle
CREATE TABLE my_table (
other_column varchar(50),
updated_at timestamp
);
CREATE OR REPLACE FUNCTION update_date()
RETURNS trigger
LANGUAGE plpgsql
COST 1
AS '
BEGIN
IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN
NEW.updated_at = now();
RETURN NEW;
ELSE
RETURN OLD;
END IF;
END;
'
;
CREATE TRIGGER update_date BEFORE
UPDATE
ON
my_table FOR EACH ROW EXECUTE PROCEDURE update_date();
INSERT INTO my_table VALUES ('other_column_insert');
UPDATE my_table SET other_column = 'other_column_update';
ALTER TABLE my_table ADD COLUMN new_colum varchar(50);
UPDATE my_table SET new_colum = 'new_colum_update'; -- this UPDATE doesn't work because of the trigger BEFORE UPDATE
-- It is possible to make it works by disabling the trigger BEFORE the first UPDATE
-- ALTER TABLE my_table DISABLE TRIGGER update_date;
Have you ever encountered this behavior ?
It's something to do with the (unnecessary) wrapping of NEW/OLD with a ROW(...) constructor:
BEGIN
IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN
-- IF NEW IS DISTINCT FROM OLD THEN
NEW.updated_at = now();
ELSE
RAISE EXCEPTION $$NOT DISTINCT: % / %$$, NEW, OLD;
END IF;
RETURN NEW;
END;
I've also moved the RETURN NEW to the end. If you try your version you should see the exceptions. If you replace it out with the commented-out one below then it works.
Now, as to why this is failing when you compare rows I'm not sure and it's too hot and late on a Friday afternoon where I am to figure it out I'm afraid.
I am going to say this is a caching problem. I modified the function to see what is going on:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table (
other_column varchar(50),
updated_at timestamp
);
CREATE OR REPLACE FUNCTION public.update_date()
RETURNS trigger
LANGUAGE plpgsql
COST 1
AS $function$
BEGIN
RAISE NOTICE 'New row %', ROW(NEW.*);
RAISE NOTICE 'Old row%', ROW(OLD.*);
RAISE NOTICE 'New.* %', (NEW.*)::text;
RAISE NOTICE 'Old.* %', (OLD.*)::text;
IF NEW.* IS DISTINCT FROM OLD.* THEN
NEW.updated_at = now();
RETURN NEW;
ELSE
RETURN OLD;
END IF;
END;
$function$;
CREATE TRIGGER update_date BEFORE
UPDATE
ON
my_table FOR EACH ROW EXECUTE PROCEDURE update_date();
INSERT INTO my_table VALUES ('other_column_insert');
UPDATE my_table SET other_column = 'other_column_update';
NOTICE: New row (other_column_update,)
NOTICE: Old row(other_column_insert,)
NOTICE: New.* (other_column_update,)
NOTICE: Old.* (other_column_insert,)
ALTER TABLE my_table ADD COLUMN new_colum varchar(50);
UPDATE my_table SET new_colum = 'new_colum_update';
NOTICE: New row (other_column_update,"2022-08-12 10:38:54.815831")
NOTICE: Old row(other_column_update,"2022-08-12 10:38:54.815831")
NOTICE: New.* (other_column_update,"2022-08-12 10:38:54.815831",new_colum_update)
NOTICE: Old.* (other_column_update,"2022-08-12 10:38:54.815831",)
It has to do with the ROW(). Even doing ROW(NEW.*)::my_table or using EXECUTE to make the query dynamic and not use caching does not work.
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();
I want to create a trigger function, which copies certain columns of an recent updated row and deletes the old data. After that I want to insert the copied columns in exact the same table in the same row (overwrite). I need the data to be INSERTED because this function will be embedded in an existing program, with predefined Triggers.
That's what I have so far:
CREATE OR REPLACE FUNCTION update_table()
RETURNS TRIGGER AS
$func$
BEGIN
WITH tmp AS (DELETE FROM table
WHERE table.id = NEW.id
RETURNING id, geom )
INSERT INTO table (id, geom) SELECT * FROM tmp;
END;
$func$ language plpgsql;
CREATE TRIGGER T_update
AFTER UPDATE OF geom ON table
EXECUTE PROCEDURE update_table();
But I get the Error message:
ERROR: cannot perform DELETE RETURNING on relation "table"
HINT: You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause.
Why I should use a rule here?
I'm using PostgreSQL 9.6
UPDATE:
A little bit of clarification. When I have two columns in my table (id, geom), after I updated geom I want to make a copy of this (new)row and insert it into the same table, while overwriting the updated row. (I'm not interested in any value before the update) I know that this is odd but I need this row to be inserted again because the program i embed this function in, listens to a INSERT statement and cannot be changed by me.
Right after you update a row, its old values will no longer be available. So, if you simply want to preserve the old row in case of an update you need to create a BEFORE UPDATE trigger, so that you can still access the OLD values and create a new row, e.g.
CREATE TABLE t (id int, geom geometry(point,4326));
CREATE OR REPLACE FUNCTION update_table() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO t (id, geom) VALUES (OLD.id,OLD.geom);
RETURN NEW;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER t_update
BEFORE UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE update_table();
INSERT INTO t VALUES (1,'SRID=4326;POINT(1 1)');
If you update the record 1 ..
UPDATE t SET geom = 'SRID=4326;POINT(2 2)', id = 2 WHERE id = 1;
UPDATE t SET geom = 'SRID=4326;POINT(3 3)', id = 3 WHERE id = 2;
.. you get a new record in the same table as you wished
SELECT id, ST_AsText(geom) FROM t;
id | st_astext
----+------------
1 | POINT(1 1)
2 | POINT(2 2)
3 | POINT(3 3)
Demo: db<>fiddle
Unrelated note: consider upgrading your PostgreSQL version! 9.6 will reach EOL in November, 2021.
First thanks to #JimJones for the answer. I´d like to post his answer modified for this purpose. This code "overwrites" the updated row by inserting a copy of itself and then deleting the old duplicate. That way I can Trigger on INSERT.
CREATE TABLE t (Unique_id SERIAL,id int, geom geometry(point,4326));
CREATE OR REPLACE FUNCTION update_table() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO t (id, geom) VALUES (NEW.id,NEW.geom);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER t_update
BEFORE UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE update_table();
CREATE OR REPLACE FUNCTION delete_table() RETURNS TRIGGER AS $$
BEGIN
DELETE FROM t a
USING t b
WHERE a.Unique_id < b.Unique_id
AND a.geom = b.geom;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER t_delete
AFTER UPDATE OF geom ON t FOR EACH ROW EXECUTE PROCEDURE delete_table();
INSERT INTO t VALUES (1,1,'SRID=4326;POINT(1 1)');
UPDATE t SET geom = 'SRID=4326;POINT(2 2)' WHERE id = 1;
I have a table with many columns and several million rows like
CREATE TABLE foo (
id integer,
thing1 text,
thing2 text,
...
stuff text);
How can I manage the relevance of dictionary of unique values of stuff column that originally populates like this:
INSERT INTO stuff_dict SELECT DISTINCT stuff from foo;
Should I manually synchronize (check if new stuff value already in stuff_dict before every insert/update) or use triggers for each insert/update/delete from foo table. In latter case, what is the best design for such a trigger(s)?
UPDATE: view does not suit here, because the SELECT * FROM stuff_dict should run as fast as possible (even CREATE INDEX ON foo(stuff) does not help much when foo has tens of millions of records).
A materialized view seems to be the simplest option for a large table.
In the trigger function just refresh the view. You can use concurrently option (see the pozs's comment below).
create materialized view stuff_dict as
select distinct stuff
from foo;
create or replace function refresh_stuff_dict()
returns trigger language plpgsql
as $$
begin
refresh materialized view /*concurrently*/ stuff_dict;
return null;
end $$;
create trigger refresh_stuff_dict
after insert or update or delete or truncate
on foo for each statement
execute procedure refresh_stuff_dict();
While the solution with a materialized view is straightforward it may be suboptimal when the table foo is modified frequently. In this case use a table for the dictionary. An index will be helpful.
create table stuff_dict as
select distinct stuff
from foo;
create index on stuff_dict(stuff);
The trigger function is more complicated and should be fired for each row after insert/update/delete:
create or replace function refresh_stuff_dict()
returns trigger language plpgsql
as $$
declare
do_insert boolean := tg_op = 'INSERT' or tg_op = 'UPDATE' and new.stuff <> old.stuff;
do_delete boolean := tg_op = 'DELETE' or tg_op = 'UPDATE' and new.stuff <> old.stuff;
begin
if do_insert and not exists (select 1 from stuff_dict where stuff = new.stuff) then
insert into stuff_dict values(new.stuff);
end if;
if do_delete and not exists (select 1 from foo where stuff = old.stuff) then
delete from stuff_dict
where stuff = old.stuff;
end if;
return case tg_op when 'DELETE' then old else new end;
end $$;
create trigger refresh_stuff_dict
after insert or update or delete
on foo for each row
execute procedure refresh_stuff_dict();
I'm stuck for days with triggers on Postgresql (and Mysql as well). I just want to insert newly filled rows to another table. The original data comes from an external form (OpenDataKit) and goes to "intermediate" tables. I can't understand why the form cannot send the data anymore once the trigger is created... Note that all actions work without the trigger, when I do the insertions by hand. I would greatly appreciate some help to understand what I am doing wrong. I am now testing with Postgresql 9.5, but I got similar issue with MySQL 5.1.
-- CREATE procedure:
CREATE OR REPLACE FUNCTION proc_natobs() RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
INSERT INTO lieu (id_lieu, wgs_lat, wgs_lon, date_obs, geom)
SELECT id_loc,"GPS_TEL_LAT", "GPS_TEL_LNG", "DATE_OBS", ST_SetSRID(ST_POINT("GPS_TEL_LNG","GPS_TEL_LAT"), 4326)
FROM "FORMULAIRE_NATOBS_REPEAT_LOC", "FORMULAIRE_NATOBS_CORE"
WHERE "FORMULAIRE_NATOBS_CORE"."_URI" = "FORMULAIRE_NATOBS_REPEAT_LOC"."_TOP_LEVEL_AURI"
AND "FORMULAIRE_NATOBS_REPEAT_LOC".id_loc IN (SELECT max(id_loc) FROM "FORMULAIRE_NATOBS_REPEAT_LOC");
INSERT INTO i_lieu_observateurs (id_lieu, id_auteur)
SELECT id_loc, CAST("AUTEUR" AS integer)
FROM "FORMULAIRE_NATOBS_CORE", "FORMULAIRE_NATOBS_REPEAT_LOC"
WHERE "FORMULAIRE_NATOBS_REPEAT_LOC"."_TOP_LEVEL_AURI" = "FORMULAIRE_NATOBS_CORE"."_URI"
AND id_loc IN (SELECT max(id_loc) FROM "FORMULAIRE_NATOBS_REPEAT_LOC")
UNION
SELECT id_loc, CAST("OBSERVATEURS" AS integer)
FROM "FORMULAIRE_NATOBS_REPEAT_LOC", "FORMULAIRE_NATOBS_REPEAT_OBSERVATEUR"
WHERE "FORMULAIRE_NATOBS_REPEAT_LOC"."_TOP_LEVEL_AURI" = "FORMULAIRE_NATOBS_REPEAT_OBSERVATEUR"."_TOP_LEVEL_AURI"
AND id_loc IN (SELECT max(id_loc) FROM "FORMULAIRE_NATOBS_REPEAT_LOC")
;
END;
$BODY$
LANGUAGE 'plpgsql';
-- CREATE the trigger:
CREATE TRIGGER trigger_natobs AFTER INSERT
ON "FORMULAIRE_NATOBS_REPEAT_LOC"
FOR EACH ROW
EXECUTE PROCEDURE proc_natobs();
So, when the ODK form inserts new rows in FORMULAIRE_NATOBS_REPEAT_LOC (for which I have created a serial ID to facilitate the SQL queries), I try to insert this row (combined with information from other intermediate tables) into table "lieu" for the first action of the trigger, and into table i_lieu_observation (composed by a double primary key) for the second action. I tested also with a trigger composed by the first action only, but it does not work either. The Android app that sends the form crashes until I remove the trigger.
Thanks in advance!
You need to use the special NEW variable in the trigger to access the newly inserted data. So you need something like:
CREATE OR REPLACE FUNCTION proc_natobs() RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
INSERT INTO lieu (id_lieu, wgs_lat, wgs_lon, date_obs, geom)
SELECT new.id_loc,"GPS_TEL_LAT", "GPS_TEL_LNG", "DATE_OBS", ST_SetSRID(ST_POINT("GPS_TEL_LNG","GPS_TEL_LAT"), 4326)
FROM "FORMULAIRE_NATOBS_CORE"
WHERE "FORMULAIRE_NATOBS_CORE"."_URI" = new."_TOP_LEVEL_AURI";
INSERT INTO i_lieu_observateurs (id_lieu, id_auteur)
SELECT new.id_loc, CAST("AUTEUR" AS integer)
FROM "FORMULAIRE_NATOBS_CORE"
WHERE new."_TOP_LEVEL_AURI" = "FORMULAIRE_NATOBS_CORE"."_URI"
UNION
SELECT new.id_loc, CAST("OBSERVATEURS" AS integer)
FROM "FORMULAIRE_NATOBS_REPEAT_OBSERVATEUR"
WHERE new."_TOP_LEVEL_AURI" = "FORMULAIRE_NATOBS_REPEAT_OBSERVATEUR"."_TOP_LEVEL_AURI";
RETURN new;
END;
$BODY$
LANGUAGE 'plpgsql';
-- CREATE the trigger:
CREATE TRIGGER trigger_natobs AFTER INSERT
ON "FORMULAIRE_NATOBS_REPEAT_LOC"
FOR EACH ROW
EXECUTE PROCEDURE proc_natobs();
Because I don't know which fields come from which tables, I cannot make the above totally correct. In the same way as I have written new.id_loc, you will need to put new.field_name for all fields coming from the formulaire_natobs_repeat_loc table.
HTH
Try this
CREATE OR REPLACE FUNCTION proc_natobs() RETURNS TRIGGER AS
$BODY$
BEGIN
IF(TG_OP = 'INSERT') THEN
INSERT INTO lieu (id_lieu, wgs_lat, wgs_lon, date_obs, geom)
SELECT id_loc,"GPS_TEL_LAT", "GPS_TEL_LNG", "DATE_OBS", ST_SetSRID(ST_POINT("GPS_TEL_LNG","GPS_TEL_LAT"), 4326)
FROM "FORMULAIRE_NATOBS_REPEAT_LOC" loc, "FORMULAIRE_NATOBS_CORE" core
WHERE core."_URI" = loc."_TOP_LEVEL_AURI"
AND loc.id_loc =new.id_loc;
INSERT INTO i_lieu_observateurs (id_lieu, id_auteur)
SELECT id_loc as id,
CAST("AUTEUR" AS integer) as auteur
FROM "FORMULAIRE_NATOBS_CORE" core, "FORMULAIRE_NATOBS_REPEAT_LOC" loc
WHERE loc."_TOP_LEVEL_AURI" = core."_URI"
AND loc.id_loc =new.id_loc;
UNION
SELECT id_loc as id,
CAST("OBSERVATEURS" AS integer) as auteur
FROM "FORMULAIRE_NATOBS_REPEAT_LOC" loc, "FORMULAIRE_NATOBS_REPEAT_OBSERVATEUR" obs
WHERE loc."_TOP_LEVEL_AURI" = obs."_TOP_LEVEL_AURI"
AND loc.id_loc =new.id_loc;
END IF;
Return new;
END;
$BODY$
LANGUAGE 'plpgsql';
-- CREATE the trigger:
CREATE TRIGGER trigger_natobs AFTER INSERT
ON "FORMULAIRE_NATOBS_REPEAT_LOC"
FOR EACH ROW
EXECUTE PROCEDURE proc_natobs();
Hope it work for you.