How do I update a summary table with a trigger? - postgresql

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();

Related

Insert 2 rows in trigger (different tables, insert 2 must references insert1)

I need to insert 2 rows into 2 different tables using a trigger but the public.user table needs to get the company id that I am inserting into the db within the same trigger. Is this possible?
My other option (i think) would be to add another trigger for after inserts on public.user and update the public user created_by column using a select from the company table.
create or replace function public.handle_new_user()
returns trigger as $$
begin
-- Insert here
insert into public.company (created_by)
values (new.id)
-- need to ref public.company just inserted in this insert statement
insert into public.users (id, email, name, company_id)
values (new.id, new.email, new.raw_user_meta_data->>'full_name', (select top 1 id from public.company where created_by = new.id));
return null;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
Postgres provides the ability to return columns from the INSERT. With that you can get the id of the company row just inserted.
create or replace function public.handle_new_user()
returns trigger as $$
declare
l_company_id public.company.id%type;
begin
-- Insert here
insert into public.company (created_by)
values (new.id)
returning id into l_company_id;
-- need to ref public.company just inserted in this insert statement
insert into public.users (id, email, name, company_id)
values (new.id, new.email, new.raw_user_meta_data->>'full_name', l_company_id);
return null;
end;
$$ language plpgsql security definer;

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;

Why this trigger is not inserting records in my table in postgres?

Here are my 3 tables.
Booking(Bookingid,date_of_booking,groupid,packageid,price)
Passenger(passengerid,groupid)
package(packageid,type)
Here is a function which will be called when trigger gets executed.
CREATE OR REPLACE FUNCTION COUNTPRICEFORTICKET3()
RETURNS TRIGGER AS
$$
DECLARE
MYID INTEGER;
MYPRICE DOUBLE PRECISION;
BEGIN
SELECT COUNT(*) AS CNT INTO MYID FROM PASSENGER WHERE GROUPID=NEW.GROUPID
GROUP BY GROUPID;
SELECT PRICE INTO MYPRICE FROM PACKAGE WHERE PACKAGEID=NEW.PACKAGEID;
NEW.PRICE:=MYPRICE * MYID;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER COUNTPRICE3 BEFORE INSERT ON BOOKING
FOR EACH ROW EXECUTE PROCEDURE COUNTPRICEFORTICKET3();
The problem is that trigger is executed successfully but when I try to insert records to booking table using INSERT query like this, it does not insert record.
INSERT INTO BOOKING VALUES(NEXTVAL('BOOKSQ'),'26-SEP-2018',1201,700,NULL);

Postgresql Trigger to insert rows

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.

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;