Postgresql Trigger to insert rows - postgresql

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.

Related

How do I update a summary table with a trigger?

I am working with the sample DVD_Rental database. I have to create a trigger on my category_performance_details table that will create a summary table every time data is added to the detail table.
These are the queries I am using to setup my tables:
DROP TABLE IF EXISTS category_performance_summary;
CREATE TABLE category_performance_summary (
genre VARCHAR(25),
total_sales numeric
);
DROP TABLE IF EXISTS category_performance_details;
CREATE TABLE if not exists category_performance_details (
category_id Int,
category_name VARCHAR(25),
film_id Int,
film_title VARCHAR(255),
film_rental_rate numeric(4,2),
inventory_id Int,
payment_id Int,
payment_amount numeric(4,2),
rental_id Int,
rental_date Timestamp
);
I want to update the summary table after every insert statement with a trigger so that it provides a summary of the total sales per category. Basically, the summary table should be the same result as:
SELECT category_name, SUM(payment_amount) FROM category_performance_details
GROUP BY category_name;
This is my procedure and trigger. For some reason, I am getting a syntax error at or near "CREATE TRIGGER". Am I approaching this trigger correctly? What is wrong with my syntax?
CREATE OR REPLACE FUNCTION update_summary_table()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
TRUNCATE TABLE category_performance_summary;
INSERT INTO category_performance_summary (genre, total_sales)
SELECT category_name, SUM(payment_amount)
FROM category_performance_details
GROUP BY category_name;
RETURN NEW;
END;
$$
CREATE TRIGGER update_summary
AFTER INSERT
ON category_performance_summary
FOR EACH STATEMENT
EXECUTE PROCEDURE update_summary_table();
You can also simply increment or decrement the total_sales in the trigger function.
In any case you have to use the NEW (resp. OLD) key word in order to refer to the values that are inserted/updated (resp. deleted) in table category_performance_summary :
CREATE OR REPLACE FUNCTION increment_summary_table()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
INSERT INTO category_performance_summary (genre, total_sales)
VALUES (NEW.category_name, 1)
ON CONFLICT (genre) DO UPDATE
SET total_sales= total_sales + 1
WHERE genre= NEW.category_name ;
RETURN NEW;
END;
$$ ;
CREATE TRIGGER increment_summary
AFTER INSERT
ON category_performance_summary
FOR EACH STATEMENT
EXECUTE PROCEDURE increment_summary_table();
CREATE OR REPLACE FUNCTION decrement_summary_table()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
UPDATE category_performance_summary
SET total_sales= total_sales - 1
WHERE genre= OLD.category_name ;
RETURN OLD;
END;
$$ ;
CREATE TRIGGER decrement_summary
AFTER DELETE
ON category_performance_summary
FOR EACH STATEMENT
EXECUTE PROCEDURE decrement_summary_table();

How to DELETE/INSERT rows in the same table using a UPDATE Trigger?

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;

How do I refer to a data_table during an insert trigger function in postgres

I am working with Postgresql und try to define a trigger function. Everytime a row is added to a special table, is should gather informations form several tables und add a DS in a different row.
This is what I got by now.
CREATE FUNCTION tf_mdo.insert_new_package()
RETURNS trigger
LANGUAGE 'plpgsql'
NOT LEAKPROOF
AS $BODY$
insert into v_triple_o_view (offerer_id, location_id)
Values (new.id, foreigntables.id where foreigntables.offerer = new.id)
$BODY$ ;
ALTER FUNCTION tf_mdo.insert_new_package()
OWNER TO myadmin;
Now I would like to insert into "location_id" a special "id" from foreigntables. Excatly that one, that has got in offerer the new.id.
What is the correct syntax for doing so?
Thanks for help!
Use a select statement for insert.
insert into v_triple_o_view (offerer_id, location_id)
SELECT new.id, f.id FROM foreigntables f where f.offerer = new.id

Inserting Data in Postgres table when another table has been inserted with multiple rows

I am new to Postgres. My problem is that I want to insert some new rows in "target table" when these new rows are inserted in a "source table".
I wrote a trigger to do exactly that but whenever the source is inserted with say 7 new rows then the trigger inserts 7x7 = 49 rows in target. Next if I insert 3 more new rows in source then the target becomes 49+3x10 = 79.
What am I doing wrong..??
Trigger function:
CREATE OR REPLACE FUNCTION public.rec_insert()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO target_table ("TIME","REGION","CITY","DISTRICT","Population")
SELECT NEW."TIME",NEW."REGION",NEW."CITY",NEW."DISTRICT",(100*(NEW."SAMPLES_MALE_Available")/(NULLIF((NEW."Total_AVAIL"-NEW."Female_AVAIL"),0)))
FROM source_table;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
And my trigger is
CREATE TRIGGER ins_same_rec
AFTER UPDATE
ON source_table
FOR EACH ROW
EXECUTE PROCEDURE rec_insert();
You must omit the FROM. Thats the reason because many rows are inserted (one for each row in the previous state of the table)
CREATE OR REPLACE FUNCTION public.rec_insert()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO target_table ("TIME","REGION","CITY","DISTRICT","Population")
SELECT NEW."TIME",NEW."REGION",NEW."CITY",NEW."DISTRICT",(100*(NEW."SAMPLES_MALE_Available")/(NULLIF((NEW."Total_AVAIL"-NEW."Female_AVAIL"),0)));
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;

Trigger insert into another table only if unique value

I have a trigger function that copy row of unique values to another table on update or insert that ALMOST work.
The trigger should only insert a new row to the sample table if the number don't exist in it before. Atm. it insert a new row to the sample table with the value NULL if the number already exist in the table. I dont want it to do anything if maintbl.number = sample.nb_main
EDIT: sample table and sample data
CREATE TABLE schema.main(
sid SERIAL NOT NULL,
number INTEGER,
CONSTRAINT sid_pk PRIMARY KEY (sid)
)
CREATE TABLE schema.sample(
gid SERIAL NOT NULL,
nb_main INTEGER,
CONSTRAINT gid_pk PRIMARY KEY (gid)
Example and desired result
schema.main schema.sample
number nb_main
234233 234233
234234 555555
234234
555555
555555
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO schema.sample(
nb_main)
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION schema.update_number()
OWNER TO postgres;
CREATE TRIGGER update_number
AFTER INSERT OR UPDATE
ON schema.maintbl
FOR EACH ROW
EXECUTE PROCEDURE schema.update_number();
I just found out that my select query is probably wrong, if I run SELECT query by itself it return one row 'NULL' but i should not?
SELECT DISTINCT(maintbl.number)
FROM schema.maintbl
WHERE NOT EXISTS (
SELECT nb_main FROM schema.sample WHERE maintbl.number = sample.nb_main);
Any good advice?
Best
If I understood correctly, you wish to append to schema.sample a number that has been inserted or updated in schema.maintbl, right?
CREATE OR REPLACE FUNCTION schema.update_number()
RETURNS trigger AS
$BODY$
BEGIN
IF (SELECT COUNT(*) FROM schema.sample WHERE number = NEW.number) = 0 THEN
INSERT INTO schema.sample(nb_main) VALUES (NEW.number);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;