How to migrate a Firebird's Trigger to PostgreSQL - postgresql

I'm migrating an entire database from Firebird to PostgreSQL and it's not rocket science. But I'm having serious trouble with triggers. Specially the Firebird's POSITION argument.
Actually, I'm searching about the POSITION behavior. I need it but in PostgreSQL.
Those are the Triggers in Firebird:
This Trigger needs to be executed first:
/* Trigger: TRG_CFE_ESTOQUE_PROCESSADO */
CREATE OR ALTER TRIGGER TRG_CFE_ESTOQUE_PROCESSADO FOR ITENS_CFE
BEFORE UPDATE POSITION 0
AS
BEGIN
IF(NEW.ITE_QTD <> OLD.ITE_QTD)THEN
BEGIN
NEW.ITE_ESTOQUE_PROCESSADO = 'N';
END
END
And this one needs to be executed after:
/* Trigger: TRG_CFE_ESTOQUE_EXCLUIDO */
CREATE OR ALTER TRIGGER TRG_CFE_ESTOQUE_EXCLUIDO FOR ITENS_CFE
BEFORE DELETE POSITION 1
AS
BEGIN
UPDATE ITENS_CFE
SET ITE_ESTOQUE_PROCESSADO = 'N'
WHERE PRO_CODIGO = OLD.PRO_CODIGO
AND CFE_CODIGO = OLD.CFE_CODIGO;
END
For now, I'm not testing it, just searching for a way to reproduce the expected behavior.

Searching again, I've found something in the PostgreSQL Documentation:
If multiple triggers of the same kind are defined for the same event, they will be fired in alphabetical order by name
And I think it will do the magic.
But is this the best way of doing it?

The standard way I've defined trigger would be like the following:
CREATE OR REPLACE FUNCTION func_table_x_after_insert()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO table_y
(id)
VALUES
(NEW.id)
;
RETURN NEW;
END;
$$ LANGUAGE PLPGSQL;
CREATE TRIGGER trig_table_x_after_insert
AFTER INSERT ON table_x
FOR EACH ROW EXECUTE PROCEDURE func_table_x_after_insert();
The function you define can handle multiple steps.

Related

Pass the query result to the function

I created a function that takes as a parameter a string by which i am looking for the desired element in the Bus table. After that i create a trigger that will fire after inserting into the Maintenance table. Here i have a problem: i specify that when changing the table, call the function and pass the last added element there, but the trigger is not created.
I looked for similar questions and saw that you need to take the query in brackets, but it did not help.
Ask for your help!
Function:
create function set_status(model_ varchar(50)) returns void as $$
update Bus set technical_condition = 'don`t work' where model = model_;
$$ LANGUAGE sql;
Trigger:
create trigger check_insert
after insert on Maintenance
for each row
execute procedure set_status((select model from Maintenance order by id_m desc limit 1));
First off your trigger function must be of the form:
create or replace function <function_name>()
returns trigger
language plpgsql
as $$
begin
...
end;
$$;
The language specification may come either before the code or after it. Moreover it must be defined returning trigger and as taking no parameters. See documentation.
You can achieve what you want by moving the select status ... query into the trigger function itself.
create or replace function set_status()
returns trigger
language plpgsql
as $$
begin
update bus
set technical_condition =
(select model
from maintenance
order by id_m desc
limit 1
) ;
return null;
end;
$$;
create trigger check_insert
after insert on maintenance
for each row
execute procedure set_status();
NOTE: Not Tested.

Postgresql - Function to prevent UPDATE from occurring

Using PostgreSQL 11.6. I want to prevent an UPDATE to occur on a given column, if a different column data meets certain criteria. I figure the best way is via an Event Trigger, before update.
Goal: if column 'sysdescr' = 'no_response' then do NOT update column 'snmp_community'.
What I tried in my function below is to skip/pass on a given update when that criteria is met. But it is preventing any updates, even when the criteria doesn't match.
CREATE OR REPLACE FUNCTION public.validate_sysdescr()
RETURNS trigger
LANGUAGE plpgsql
AS $function$BEGIN
IF NEW.sysdescr = 'no_response' THEN
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$function$;
Note: I was thinking using some type of 'skip' action may be best, to make the function more re-usable. But if I need to call out the specific column to not update (snmp_community) that's fine.
Change the procedure to:
CREATE OR REPLACE FUNCTION public.validate_sysdescr()
RETURNS trigger
LANGUAGE plpgsql
AS $function$BEGIN
IF NEW.sysdescr = 'no_response' THEN
NEW.snmp_community = OLD.snmp_community ;
END IF;
RETURN NEW;
END;
$function$;
And associate it to a common on update trigger:
CREATE TRIGGER validate_sysdescr_trg BEFORE UPDATE ON <YOUR_TABLE>
FOR EACH ROW
EXECUTE PROCEDURE public.validate_sysdescr();

Why postgresql trigger doesn't launch?

I use postgresql 11.
I think my problem comes from a trigger that doesn't do an update so the next trigger doesn't launch.
I have a table projet with columns : projet_temps_doe, projet_temps_etudes, projet_temps_globale.
The goal is to update each columns depending on other columns values.
The idea is : projet_temps_globale = projet_temps_doe + projet_temps_etudes.
I have a first trigger on projet_temps_doe which works perfectly:
create function temps_globale_doe() returns trigger
language plpgsql
as
$$
begin
new.projet_temps_globale_doe := new.projet_temps_doe_gc_bts + new.projet_temps_doe_gc_nra;
return new;
end;
$$;
CREATE TRIGGER temps_globale_doe
BEFORE UPDATE OF projet_temps_doe_gc_bts, projet_temps_doe_gc_nra
ON public.projet
FOR EACH ROW
EXECUTE PROCEDURE public.temps_globale_doe();
I have a similar trigger on projet_temps_etudes which works perfectly too.
Then the trigger I struggle with on projet_temps_globale :
create trigger maj_temps_globale_projet
before update of projet_temps_doe, projet_temps_etudes on projet
for each row
execute procedure maj_temps_globale_projet();
create or replace function maj_temps_globale_projet()returns trigger
language plpgsql
as
$$
begin
new.projet_temps_globale := new.projet_temps_doe + new.projet_temps_etudes;
raise info 'TEST!!';
return new;
end;
$$;
When projet_temps_doe and/or projet_temps_etudes are updated via triggers my last trigger doesn't launch. However when I manually change projet_temps_doe and/or projet_temps_etudes values the trigger maj_temps_globale_projet is fired.
I want to learn from this, so, if possible, explain to me what I'm doing wrong here, or if my approach is lacking insight.
The doc says
The trigger will only fire if at least one of the listed columns is
mentioned as a target of the UPDATE command.
The column projet_temps_globale_doe is not part of the update command but is rather set in another trigger via new.projet_temps_globale_doe = ... so the trigger on this particular column is not called.
It would be easier to have only one trigger on the entire table that sets the 3 derived values.

Postgresql: run trigger AFTER update FOR EACH STATEMENT ONLY if data changed

In Postgresql I can have two kinds of triggers: FOR EACH ROW and FOR EACH STATEMENT. If I do a FOR EACH ROW trigger, I can add a WHERE clause something like OLD.* != NEW.* so it only fires if something has actually changed. Is there any way to do something similar with STATEMENT level triggers? I know I can't do the same thing since OLD and NEW aren't available, but I was thinking perhaps there might be a way to check the number of rows changed from within my function itself or the like.
Usage case: I am using the postgresql NOTIFY system to notify my app when data changes. Ideally, the app would get a single notification each time one or more records changes, and not get notified at all if data stays the same (even if an UPDATE was run). With a basic AFTER UPDATE FOR EACH STATEMENT trigger, I am getting notified every time an update statement runs - even if it doesn't actually change anything.
You should create two triggers: before update for each row and after update for each statement.
The first trigger checks if the table is being updated and sets a flag if so.
The second trigger checks the flag and performs notify if it was set.
You can use a custom configuration parameter as the flag (e.g. flags.the_table).
The solution is simple and safe, as the parameter is local in the current session.
create or replace function before_each_row_on_the_table()
returns trigger language plpgsql
as $$
begin
if new <> old then
set flags.the_table to 'on';
end if;
return new;
end $$;
create or replace function after_each_statement_on_the_table()
returns trigger language plpgsql
as $$
begin
if current_setting('flags.the_table', true) = 'on' then
notify your_channel, 'the_table was updated';
set flags.the_table to 'off';
end if;
return null;
end $$;
create trigger before_each_row_on_the_table
before update on the_table
for each row execute procedure before_each_row_on_the_table();
create trigger after_each_statement_on_the_table
after update on the_table
for each statement execute procedure after_each_statement_on_the_table();
The function current_setting() with two arguments is available in Postgres 9.6 or later.

PostgreSQL trigger to replicate changes from one table to another

I have a database named info. In the database I have 4 different tables info1, info2, info3 and info4.
I want to create a stored procedure so that whenever I make changes in table info1 (like INSERT, DELETE or UPDATE) the same changes should appear in the other three tables.
I am using PostgreSQL for this and I don't know how to perform this query.
Please explain it with an example.
Thanks in advance!
Basically you need to read the manual to understand what you are doing. #Michael provided links.
There are many different ways how you can go about this. Here are two typical examples for UPDATE and DELETE:
Create trigger function for UPDATE:
CREATE OR REPLACE FUNCTION trg_info1_upaft()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE info2
SET col1 = NEW.col1
--more?
WHERE info2.info1_id = NEW.info1_id;
UPDATE info3
SET col1 = NEW.col1
--more?
WHERE info3.info1_id = NEW.info1_id;
-- more?
RETURN NULL; -- because trigger is meant for AFTER UPDATE
END;
$BODY$
LANGUAGE plpgsql;
Create the trigger making use of it:
CREATE TRIGGER upaft
AFTER UPDATE
ON info1
FOR EACH ROW
EXECUTE PROCEDURE trg_info1_upaft();
For DELETE:
CREATE OR REPLACE FUNCTION trg_info1_delaft()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM info2
WHERE info1_id = OLD.info1_id;
-- more?
RETURN NULL; -- because trigger is meant for AFTER DELETE
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER delaft
AFTER DELETE
ON info1
FOR EACH ROW
EXECUTE PROCEDURE trg_info1_delaft();
For such changes you should use trigger: http://www.postgresql.org/docs/current/interactive/triggers.html
You will have to create function that inserts/updates/deletes new data into other tables and then show PostgreSQL with CREATE TRIGGER http://www.postgresql.org/docs/current/interactive/sql-createtrigger.html to call that function every time data in source table is changed.