Copy last row field value to another column table with trigger - postgresql

I'm working in PostgreSQL using a trigger. I have 2 tables here, invoice_items and invoice. In the table 'invoice' data is inserted from the system, then the table 'invoice_items' is filled using a trigger to get the values from the inserted data of the table 'invoice'. Each insert from the system system consists of two rows like this:
table 'invoice':
id invoice_date statement amount
1 2018-10-03 Insert 5
2 2018-10-03 Update 6
then table 'invoice_items' after trigger the invoice data is inserted:
id total statement
1 5 Insert
2 6 Update
trigger:
create trigger do_fetch
after insert
on invoices
for each row
execute procedure insert_amount();
function insert_amount():
create function insert_amount()
returns trigger
language plpgsql
as $$
BEGIN
INSERT INTO invoice_items (total, statement) VALUES (new.amount, new.statement);
return null;
END;
$$;
But I just want to get last inserted data of 'invoices' to trigger 'invoice_items' to achieve a result like this:
id invoice_date statement amount
1 2018-10-03 Insert 5
2 2018-10-03 Update 6
id total statement
1 6 Update
I have tried to edit the trigger to this:
create constraint trigger do_fetch
after insert
on invoices
deferrable
for each row
execute procedure insert_amount();
but I still get 2 row inserted to 'invoice_items' table - not only the last row, any clue ? Thank you

Here is a example - you just need to adjust your sql with the desired tablenames and columnnames:
test=# create table bla1(ts timestamp, content text);
CREATE TABLE
test=*# create table bla1_log(ts timestamp, content text);
CREATE TABLE
test=*# create or replace function trg_bla1() returns trigger as $$begin insert into
bla1_log values (new.ts, new.content); return new; end; $$language plpgsql;
CREATE FUNCTION
test=*# create trigger trg1 after insert on bla1 for each row when (NEW.content =
'Update') execute procedure trg_bla1();
CREATE TRIGGER
test=*# insert into bla1 values (now(), 'bla');
INSERT 0 1
test=*# commit;
COMMIT
test=# insert into bla1 values (now(), 'Insert');
INSERT 0 1
test=*# commit;
COMMIT
test=# insert into bla1 values (now(), 'Update');
INSERT 0 1
test=*# select * from bla1;
ts | content
----------------------------+---------
2018-11-08 12:32:28.803439 | bla
2018-11-08 12:33:30.355516 | Insert
2018-11-08 12:33:38.451548 | Update
(3 rows)
test=*# select * from bla1_log ;
ts | content
----------------------------+---------
2018-11-08 12:33:38.451548 | Update
(1 row)
test=*#
EDIT:
As of your lack of replies and showing an effort to adjust your code according to my answer, I'm not sure if thats even worth my time - but here's what should work for you..
create table invoice(id serial, invoice_date text, statement text, amount integer);
create table invoice_items(id serial, total integer, statement text);
create or replace function insert_amount() returns trigger as $$begin
insert into invoice_items values (id, total, statement); return new; end;
create trigger mytrigger after insert on invoice for each row when (NEW.statement = 'Update') execute procedure insert_amount();

Related

several columns with each value unique in all (each) of these columns ; treated by 1 transaction

I am searching the mean to do, in Postgresql, that each value in each one of several columns is unique in all (each) of these columns.
Example, with 2 columns :
col_1 col_2
--------------
a b # ok
c d # ok
e # ok
f a # forbidden
b # forbidden
b # forbidden
I need that each writing in these columns is treated by 1 transaction, especially (for some row) :
copy col_2 in col_1 and delete col_2
Has someone an idea about ?
This probably should be a comment, but then it is too long and I cannot format the code example there. You cannot get a unique constraint nor index across multiple columns. You may be able to with a trigger, but even there it is not simple:
create or replace function unique_over_2col()
returns trigger
language plpgsql
as $$
begin
if exists ( select null
from test
where new.col_1 = col_1
or new.col_1 = col_2
or new.col_2 = col_1
or new.col_2 = col_2
)
then
return null;
else
return new;
end if;
end;
$$;
create trigger test_biur
before insert or update
on <your table name here>
for each row
execute function unique_over_2col();
Your trigger will specifically have to compare every new.column against every existing column. The above just does so with the 2 columns you mentioned and that leads to 4 comparisons. Your several columns will expand this dramatically. I'll repeat the advice by #Bergi normalize your schema.
BTW: please explain copy col_2 in col_1 and delete col_2 it is totally meaningless. Perhaps it would be better to explain the business issue you are faced with rather than how you are trying to solve it.
A bit ugly but working solution:
CREATE TABLE tablename (col1 integer, col2 integer);
CREATE OR REPLACE FUNCTION pr_tablename_insertuniqueonly()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
v_new_values integer[] = ARRAY[NEW.col1, NEW.col2];
BEGIN
IF (NEW.col1=NEW.col2) THEN
RETURN null;
END IF;
IF EXISTS(SELECT 1 FROM tablename t WHERE t.col1 = ANY(v_new_values) OR t.col2 = ANY(v_new_values)) THEN
RETURN null;
ELSE
RETURN NEW;
END IF;
RETURN NEW;
END;
$$;
CREATE OR REPLACE TRIGGER tr_iine_tablename BEFORE INSERT ON tablename FOR EACH ROW EXECUTE PROCEDURE pr_tablename_insertuniqueonly();
stack=# insert into tablename values (1,1);
INSERT 0 0
stack=# insert into tablename values (1,2);
INSERT 0 1
stack=# insert into tablename values (3,2);
INSERT 0 0
stack=# insert into tablename values (3,1);
INSERT 0 0
stack=# insert into tablename values (3,4);
INSERT 0 1
stack=# select * from tablename;
col1 | col2
1 | 2
3 | 4
(2 rows)

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

user creation dumping using trigger and fucntions

As PostgreSQL doesn't dump object creation date so I want to manually dump user creation date by using trigger and functions. I have created trigger and functions but it's not working.
CREATE TABLE user_audits (
usesysid INT GENERATED ALWAYS AS IDENTITY,
usename varchar NOT NULL,
created_on TIMESTAMP(6) NOT NULL
);
============================
CREATE OR REPLACE FUNCTION user_creation()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF NEW.usename <> OLD.usename THEN
INSERT INTO user_audits(usesysid,usename,created_on)
VALUES(usesysid,usename,now());
END IF;
RETURN NEW;
END;
$$
=================================
CREATE TRIGGER user_creation
BEFORE UPDATE
ON user
FOR EACH ROW
EXECUTE PROCEDURE user_creation();
This is important for audit purpose, for now I am using log file to check creation date but it will rotate after sometime.
Please suggest the better way to dump user creation date in table so that I can retrieve the information anytime.
Thanks
I created a similar excercise with the following tables:
The user_tbl table having only a identity column usersysid and the username
CREATE TABLE user_tbl (
usersysid INT GENERATED ALWAYS AS IDENTITY,
username varchar NOT NULL
);
The user_audits table, slightly modified version of yours: where i added an id identity field. I removed the identity from the usersysid field (since it'll be populated with the one coming from user_tbl)
CREATE TABLE user_audits (
id INT GENERATED ALWAYS AS IDENTITY,
usersysid INT,
username varchar NOT NULL,
created_on TIMESTAMP(6) NOT NULL
);
Now the function, I check if the OLD.username is null, this means that is an insert, if NEW.username <> OLD.username then is an update.
CREATE OR REPLACE FUNCTION user_creation()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF OLD.username is null OR NEW.username <> OLD.username THEN
INSERT INTO user_audits(usersysid,username,created_on)
VALUES(NEW.usersysid,NEW.username,now());
END IF;
RETURN NEW;
END;
$$
;
And finally the trigger, which is fired both on INSERT or UPDATE
CREATE TRIGGER user_creation
BEFORE INSERT OR UPDATE
ON user_tbl
FOR EACH ROW
EXECUTE PROCEDURE user_creation();
Now if I create two new rows and update one with the following
insert into user_tbl (username) values('Carlo');
insert into user_tbl (username) values('Gianni');
update user_tbl set username='Giorgio' where usersysid=1;
I end up with the user_tbl containing the 2 expected rows
defaultdb=> select * from user_tbl;
usersysid | username
-----------+----------
2 | Gianni
1 | Giorgio
(2 rows)
and the user_audits tables containing 3 rows (2 for the insert + 1 for the update)
defaultdb=> select * from user_audits;
id | usersysid | username | created_on
----+-----------+----------+----------------------------
1 | 1 | Carlo | 2021-06-04 13:57:44.810889
2 | 2 | Gianni | 2021-06-04 13:58:14.680878
3 | 1 | Giorgio | 2021-06-04 13:58:44.702364
(3 rows)

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;

Setting up postgres ts_vector column

I have a search table in postgres with a ts_vector column. It looks like when I insert a dstring to this column is vectorizes it, but it doesn't do any stemming or removal of stop words:
test=# create table sample_ts_vec ( id varchar(255), tsv tsvector);
CREATE TABLE
test=# insert into sample_ts_vec values ('t1234', 'this is a test');
INSERT 0 1
test=# select * from sample_ts_vec;
id | tsv
-------+------------------------
t1234 | 'a' 'is' 'test' 'this'
(1 row)
test=# insert into sample_ts_vec values ('t1235', to_tsvector('this is a test'));
INSERT 0 1
test=# select * from sample_ts_vec;
id | tsv
-------+------------------------
t1234 | 'a' 'is' 'test' 'this'
t1235 | 'test':4
(2 rows)
You'll notice that in the second insert, the 3 stop words are removed, and the word is stemmed (in this case, no stemming necessary), whereas in the first example each word gets added. How can I apply the to_tsvector function automagically to the string value prior to insert?
Jasen's answer was close, but it had a few important errors - here's the corrected version:
CREATE FUNCTION tsvfix() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
BEGIN
NEW.tsv=to_tsvector(NEW.tsv);
RETURN NEW;
END
$$;
CREATE TRIGGER "tsvfix" BEFORE UPDATE OR INSERT ON "sample_ts_vec" FOR EACH ROW EXECUTE PROCEDURE tsvfix();
Even this doesn't work however. I get an error ERROR: function to_tsvector(tsvector) does not exist
you could create a TRIGGER for ON UPDATE OR INSERT
assuming the table has a column data which you want to make a tsv index on, something like this
CREATE FUNCTION tsvfix() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
BEGIN
NEW.tsv=to_tsvector(NEW.data);
RETURN NEW;
END
$$;
CREATE TRIGER "tsvfix" ON UPDATE OR INSERT TO "sample_ts_vec" EXECUTE PROCEDURE tsvfix;