Calling a procedure when something happens - postgresql

I just want to know if i can call a procedure automatically when something happens. In particular, i would want to call a function that deletes records from a table when now() timestamp is one month past the timestamp atribute of the record. Something like
create or replace procedure eliminate_when_time_past()
as $$
begin
if (now() is one month past the record timestamp) then
delete from preƱadas where hierro = ...
end if;
end;
$$
language plpgsql
Is it possible?

Related

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 is compiled PL/pgSQL block?

do $$
declare
tm1 timestamp without time zone;
tm2 timestamp without time zone;
begin
select localtimestamp(0) into tm1;
for i in 1..200000000 loop
--just waiting several second
end loop;
select localtimestamp(0) into tm2;
raise notice '% ; %', tm1, tm2;
end;
$$ language plpgsql
Why gives this procedure same values for tm1 and tm2 ?
Is not executed this code step by step?
From the manual
These SQL-standard functions all return values based on the start time of the current transaction [...] Since these functions return the start time of the current transaction, their values do not change during the transaction. This is considered a feature: the intent is to allow a single transaction to have a consistent notion of the "current" time, so that multiple modifications within the same transaction bear the same time stamp
(Emphasis mine)
You probably want clock_timestamp()

Triger update function when column updates in Postgres

I have users table with columns status_id (int), additional_status(int) and status_changed(DATE).
I want to autoupdate status_changed field every time when status_id or additional_status changes.
Here is what I have by now:
CREATE OR REPLACE FUNCTION update_status_changed()
RETURNS TRIGGER
AS $$
BEGIN
NEW.status_changed := CURRENT_TIMESTAMP;
RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER set_update_status_changed
AFTER INSERT OR UPDATE OF status_id, additional_status ON users
FOR EACH ROW
EXECUTE PROCEDURE update_status_changed();
I'm not sure if the syntax is correct. When I change my status_id from phpPgAdmin - status_changed stays NULL. What am I'm missing?, help pls.

Postgres - update statement as a trigger

I've been playing around for the last hour or more trying to put an update statement into a trigger. I understand the concept of an UPDATE statement and the below works just fine
UPDATE cars SET country = 'France';
What I want is to put this into a trigger so that when the cars table is updated, the column country will automatically be updated with France.
I've played around with adapting Functions and Triggers that I've found out on the interweb but I'm obviously making the statement wrong as either they don't execute or they execute but don't update the country field when a new record is added.
CREATE FUNCTION update_country () RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
UPDATE cars SET country = 'France' WHERE id = New.id;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql; --The trigger used to update a table.
CREATE TRIGGER update_country_col BEFORE UPDATE ON cars FOR EACH ROW EXECUTE PROCEDURE update_country();
The above scripts executes but does not add France to the country column.
The function was adapted from a statement that I found out on the web.
Postgres 9.1.
I know that the answer is going to be so simple!
In update triggers you should modify NEW record.
Also, you may need to return NEW record from procedure.
So, you should use following procedure instead of yours:
CREATE FUNCTION update_country () RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
NEW.country = 'France';
END IF;
RETURN NEW;
END; $$ LANGUAGE plpgsql;