Postgresql - Function to prevent UPDATE from occurring - postgresql

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

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.

SELECT in cascaded AFTER DELETE trigger returning stale data in Postgres 11

I have an AFTER INSERT/UPDATE/DELETE trigger function which runs after any change to table campaigns and triggers an update on table contracts:
CREATE OR REPLACE FUNCTION update_campaign_target() RETURNS trigger AS $update_campaign_target$
BEGIN
UPDATE contracts SET updated_at = now() WHERE contracts.contract_id = NEW.contract_id;
END;
$update_campaign_target$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS update_campaign_target ON campaigns;
CREATE TRIGGER update_campaign_target AFTER INSERT OR UPDATE OR DELETE ON campaigns
FOR EACH ROW EXECUTE PROCEDURE update_campaign_target();
I have another trigger on table contracts that runs BEFORE UPDATE. The goal is to generate a computed column target which displays either contracts.manual_target (if set) or SUM(campaigns.target) WHERE campaign.contract_id = NEW.contract_id.
CREATE OR REPLACE FUNCTION update_contract_manual_target() RETURNS trigger AS $update_contract_manual_target$
DECLARE
campaign_target_count int;
BEGIN
IF NEW.manual_target IS NOT NULL
THEN
NEW.target := NEW.manual_target;
RETURN NEW;
ELSE
SELECT SUM(campaigns.target) INTO campaign_target_count
FROM campaigns
WHERE campaigns.contract_id = NEW.contract_id;
NEW.target := campaign_target_count;
RETURN NEW;
END IF;
END;
$update_contract_manual_target$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS update_contract_manual_target ON contracts;
CREATE TRIGGER update_contract_manual_target BEFORE INSERT OR UPDATE ON contracts
FOR EACH ROW EXECUTE PROCEDURE update_contract_manual_target();
This works as expected on INSERT and UPDATE on campaigns, but does not work on DELETE. When a campaign is deleted, the result of SUM(campaigns.target) in the second trigger includes the deleted campaign's target, and thus does not update the contracts.target column to the expected value. A second update of contracts will correctly set the value.
Three questions:
Why doesn't this work?
Is there a way to achieve the behavior I'm looking for using triggers?
For this type of data synchronization, is it better to achieve this using triggers or views? Triggers make sense to me because this is a table that we will read many magnitudes of times more than we'll write to it, but I'm not sure what the best practices are.
The reason this doesn't work is the usage of NEW.contract_id in the AFTER DELETE trigger:
UPDATE contracts SET updated_at = now() WHERE contracts.contract_id = NEW.contract_id;
Per the Triggers on Data Changes documentation, NEW is NULL for DELETE triggers.
Updating the code to use OLD instead of NEW fixes the issue:
CREATE OR REPLACE FUNCTION update_campaign_target() RETURNS trigger AS $update_campaign_target$
BEGIN
IF TG_OP = 'DELETE'
THEN
UPDATE contracts SET updated_at = now() WHERE contracts.contract_id = OLD.contract_id;
ELSE
UPDATE contracts SET updated_at = now() WHERE contracts.contract_id = NEW.contract_id;
END IF;
RETURN NULL;
END;
$update_campaign_target$ LANGUAGE plpgsql;
Thanks to Anthony Sotolongo and Belayer for your help!

PostreSQL trigger disallow an action without exceptions

Trying to solve a problem for a university assignment. Each row in the table has a date field, and I need to disallow deleting rows that have this field less than 5 years old. Using a trigger is required and it must not raise exceptions. How can I do it? I tried something like this but it doesn't work:
create or replace function no_change()
returns trigger as $no_change$
begin
if current_timestamp - old.date <= interval '5y' then
new = old;
end if;
return new;
end;
$no_change$ language plpgsql;
create trigger no_change after delete on wiz
for each row execute procedure no_change();
Not tested, but should work.
create or replace function no_change()
returns trigger as $no_change$
begin
if current_timestamp - old.date <= interval '5y' then
RETURN NULL;
ELSE
RETURN OLD;
end if;
end;
$no_change$ language plpgsql;
create trigger no_change BEFORE delete on wiz
for each row execute procedure no_change();
In the PostgreSQL docs:
Row-level triggers fired BEFORE can return null to signal the trigger manager to skip the rest of the operation for this row (i.e., subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does not occur for this row).
<...>
In the case of a before-trigger on DELETE, the returned value has no direct effect, but it has to be nonnull to allow the trigger action to proceed. Note that NEW is null in DELETE triggers, so returning that is usually not sensible. The usual idiom in DELETE triggers is to return OLD.
So just return NULL in cases the date is less then 5 days old.

How to migrate a Firebird's Trigger to 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.

A trigger that detects that an UPDATE wouldn't change a row

I wrote the following trigger:
CREATE FUNCTION trig_func() RETURNS trigger AS $$
BEGIN
IF NEW = OLD
THEN -- update would do nothing, doing something...
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trig BEFORE UPDATE ON some_table
FOR EACH ROW EXECUTE PROCEDURE trig_func();
It makes it clear what I'd like to achieve, but what is the proper thing to put in place of NEW = OLD?
The is distinct from operator can compare complete rows and will handle nulls correctly.
So you want
if new is not distinct from old then
...
end if;