How to set a trigger in PostgreSQL? - postgresql

I have the tables show here -> How can I display if an airline departs from all the airport in my DB in Postgresql?
I've update the airlines table like below
ALTER TABLE airlines
ADD COLUMN count_flight INTEGER;
Now I need to update the column count_flight with the number of flight for each airline that are already stored in volo table.
How can I set a trigger that do what I need + update the value of count_flight every time I insert a new row in volo table?
EDIT
I've set a trigger like below and it works
CREATE FUNCTION update_flight() RETURNS TRIGGER AS $updateN_voli$
DECLARE
airline_name varchar;
BEGIN
IF (TG_OP = 'INSERT') THEN
airline_name = NEW.airline;
UPDATE airlines
SET count_flight = count_flight + 1
WHERE airline_name = airlines.airline_name;
END IF;
RETURN NULL;
END;
$updateN_voli$ LANGUAGE plpgsql;
/* Creo il trigger */
CREATE TRIGGER update_flight
AFTER INSERT ON volo
FOR EACH ROW
EXECUTE PROCEDURE update_flight();
Now I can't figure out how can I update the value with the existing flight. As I said in the comment below I have found a possible solution but is a bit complex if I have a big amount of row in table volo.

I've found the answer:
This is for update the column with existing data in volo table:
DO
$$
DECLARE
a varchar;
BEGIN
FOR a IN select airline from airlines LOOP
UPDATE airlines
SET count_flight = (SELECT COUNT (airline) FROM volo WHERE airline = a)
WHERE airline = a;
END LOOP;
END;
$$;
This is for update te airlines table every time a new flight is add to volo table:
CREATE FUNCTION update_newVoli() RETURNS TRIGGER AS $updateN_voli$
DECLARE
name_airline varchar;
BEGIN
IF (TG_OP = 'INSERT') THEN
name_airline = NEW.airline;
UPDATE compagnie
SET count_flight = count_flight + 1
WHERE name_airline = airlines.airline;
END IF;
RETURN NULL;
END;
$updateN_voli$ LANGUAGE plpgsql;
CREATE TRIGGER update_newVoli
AFTER INSERT ON volo
FOR EACH ROW
EXECUTE PROCEDURE update_newVoli();

Related

New columns can't be updated if a trigger BEFORE UPDATE is triggered

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.

Postgres update trigger

I've problem with a trigger function in postgresql.
Here my simple code.
CREATE TABLE specie
(specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE);
CREATE TABLE rilevatore
(rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT);
CREATE TABLE evento_investimento
(evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id));
CREATE VIEW inserimento_dati_vista AS
SELECT row_number() OVER ()::integer AS gid,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
specie.nome_comune,
specie.nome_scientifico
FROM evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
CREATE OR REPLACE FUNCTION inserimento_dati_fun_2() RETURNS trigger AS $$
BEGIN
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome)
VALUES (NEW.nome_cognome);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
INSERT INTO evento_investimento (data, ora, rilevatore_id, specie_id)
VALUES (NEW.data,NEW.ora,
(SELECT rilevatore_id FROM rilevatore WHERE rilevatore.nome_cognome = NEW.nome_cognome),
(SELECT specie_id FROM specie WHERE specie.nome_comune = NEW.nome_comune));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_dati_fun_trg
instead of insert on inserimento_dati_vista for each row EXECUTE procedure inserimento_dati_fun_2();
Now, I want to add a function that allow to update all the tables by using the view inserimento_dati_vista.
I've tried with a simple code to update only the data column
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento
SET data = new.data;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger update_dati_fun_2_trg
instead of update on inserimento_dati_vista for each row EXECUTE procedure update_dati_fun_2();
However when I perfomr the query in order to update only a row, the trigger update all the rows in the table. Here some code to fill data.
INSERT INTO inserimento_dati_vista
(data, ora, nome_cognome, nome_comune, nome_scientifico)
VALUES
('2020-01-01', '16:54:00','mario', 'lupo', 'Canis lupus'),
('2020-01-02', '13:54:00','luca', 'lontra', 'Lutra lutra');
UPDATE inserimento_dati_vista
SET data = '2021-01-02' where nome_cognome = 'luca'
Update function is:
CREATE OR REPLACE FUNCTION update_dati_fun_2() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF old.data is distinct from new.data then
UPDATE evento_investimento e
SET data = new.data
FROM rilevatore r
WHERE nome_cognome = new.nome_cognome AND r.rilevatore_id = e.rilevatore_id;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

How To Restrict Delete using PL/pgSQL trigger?

If the client user is trying to delete more than 5 records from a Table i want to restrict that using a trigger. I have a basic idea to do that but i don't know how to implement the Idea. I appreciate any HELP.
Basic Idea : In Trigger IF TG_OP = Delete and the count of records to be deleted are more than 5 then Restrict.
CREATE TRIGGER adjust_count_trigger BEFORE DELETE ON schemaname.tablename
FOR EACH ROW EXECUTE PROCEDURE public.adjust_count();
CREATE OR REPLACE FUNCTION adjust_count()
RETURNS TRIGGER AS
$$
DECLARE
num_rows int;
num_rows1 int;
BEGIN
IF TG_OP = 'DELETE' THEN
EXECUTE 'select count(*) from '||TG_TABLE_SCHEMA ||'.'||TG_RELNAME ||' where oid = old.oid ' into num_rows ;
IF num_rows > 5 Then
RAISE NOTICE 'Cannot Delete More than 5 Records , % ', num_rows ;
END IF ;
END IF ;
RETURN OLD;
END;
$$
LANGUAGE 'plpgsql';
In earlier versions of Postgres you can simulate a transition table introduced in Postgres 10. You need two triggers.
create trigger before_delete
before delete on my_table
for each row execute procedure before_delete();
create trigger after_delete
after delete on my_table
for each statement execute procedure after_delete();
In the first trigger create a temp table and insert a row into it:
create or replace function before_delete()
returns trigger language plpgsql as $$
begin
create temp table if not exists deleted_rows_of_my_table (dummy int);
insert into deleted_rows_of_my_table values (1);
return old;
end $$;
In the other trigger count rows of the temp table and drop it:
create or replace function after_delete()
returns trigger language plpgsql as $$
declare
num_rows bigint;
begin
select count(*) from deleted_rows_of_my_table into num_rows;
drop table deleted_rows_of_my_table;
if num_rows > 5 then
raise exception 'Cannot Delete More than 5 Records , % ', num_rows;
end if;
return null;
end $$;
The above solution may seem a bit hacky but it is safe if only the temp table does not exist before delete (do not use the same name of the temp table for multiple tables).
Test it in rextester.
You can easily do that with the new transition relation feature from PostgreSQL v10:
CREATE OR REPLACE FUNCTION forbid_more_than() RETURNS trigger
LANGUAGE plpgsql AS
$$DECLARE
n bigint := TG_ARGV[0];
BEGIN
IF (SELECT count(*) FROM deleted_rows) <= n IS NOT TRUE
THEN
RAISE EXCEPTION 'More than % rows deleted', n;
END IF;
RETURN OLD;
END;$$;
CREATE TRIGGER forbid_more_than_5
AFTER DELETE ON mytable
REFERENCING OLD TABLE AS deleted_rows
FOR EACH STATEMENT
EXECUTE PROCEDURE forbid_more_than(5);

Error trigger. Trigger without return. PostgreSQL

I created trigger and function.
CREATE TRIGGER trigger_update_quantity AFTER INSERT ON orderitem
FOR EACH ROW EXECUTE PROCEDURE update_quantity();
CREATE OR REPLACE FUNCTION update_quantity() RETURNS TRIGGER AS $quantity_update$
DECLARE
flower INT;
BEGIN
flower = New.flower_num;
UPDATE product SET quantity = quantity - New.quantity
WHERE flower_num = flower;
END;
$quantity_update$
LANGUAGE plpgsql;
When I try to insert a row into a table. Everything i have the following error.
INSERT INTO "public"."orderitem" ("order_num", "flower_num", "quantity", "total_price") VALUES (?, ?, ?, ?);
[2F005] ERROR: control reached end of trigger procedure without RETURN
Where: PL/pgSQL function update_quantity()
Here's what I would write (not tested):
CREATE OR REPLACE FUNCTION update_quantity() RETURNS TRIGGER AS $quantity_update$
DECLARE
flower INT;
BEGIN
flower = New.flower_num;
UPDATE product SET quantity = quantity - New.quantity
WHERE flower_num = flower;
RETURN NEW;
END;
$quantity_update$
LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_quantity AFTER INSERT ON orderitem
FOR EACH ROW EXECUTE PROCEDURE update_quantity();
how is AFTER TRIGGER, you can return RETURN OLD; or RETURN NULL;

Postgres Trigger - Working with 2 different tables

I have 2 tables:
Books - isbn (PK), title, qty-in-stock
Orderlist - ordernum (PK), isbn, quantity
My goal is to insert a record into orderlist and then, if matching isbns, add the quantity to qty-in-stock
My function and trigger is not correct -
CREATE OR REPLACE FUNCTION books_upd() RETURNS trigger as $bookupd$
BEGIN
--IF THE ISBN MATCHES BETWEEN BOOKS AND ORDERLIST
-- ADD THE EXISTING QTY_IN_STOCK (BOOKS) TO QUANTITY (ORDERLIST)
QTY_IN_STOCK:=QTY_IN_STOCK+QUANTITY;
--END IF
RETURN NEW;
$bookupd$ LANGUAGE plpgsql;
CREATE TRIGGER books_upd
BEFORE INSERT OR UPDATE on orderlist
FOR EACH ROW
EXECUTE PROCEDURE books_upd();
Can anyone help?
The trigger function is quite complicated because of different cases of insert and update.
When a user updates the order, then the function should:
add to qty_in_stock the difference between new and old quantities if isbns are the same or
subtract old quantity from qty_in_stock of one book and add new quantity to qty_in_stock of another book when isbns are different.
Additionally, the function should reject changes if the given isbn does not exist in books.
CREATE OR REPLACE FUNCTION books_upd()
RETURNS trigger as $bookupd$
DECLARE
v_quantity int;
BEGIN
v_quantity = NEW.quantity;
IF TG_OP = 'UPDATE' THEN
IF OLD.isbn != NEW.isbn THEN
UPDATE books
SET qty_in_stock = qty_in_stock- OLD.quantity
WHERE isbn = OLD.isbn;
ELSE
v_quantity = NEW.quantity- OLD.quantity;
END IF;
END IF;
UPDATE books
SET qty_in_stock = qty_in_stock+ v_quantity
WHERE isbn = NEW.isbn;
IF NOT FOUND THEN
RAISE EXCEPTION 'Uknown isbn';
END IF;
RETURN NEW;
END;
$bookupd$ LANGUAGE plpgsql;
Read more about NEW, OLD and TG_OP.
The trigger function only for insert is really simple:
CREATE OR REPLACE FUNCTION books_upd()
RETURNS trigger as $bookupd$
BEGIN
UPDATE books
SET qty_in_stock = qty_in_stock+ NEW.quantity
WHERE isbn = NEW.isbn;
RETURN NEW;
END;
$bookupd$ LANGUAGE plpgsql;