Triger update function when column updates in Postgres - postgresql

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.

Related

Postgres update column value to time.Now()

I need to update column paid_at to time.Now() on table orders when column status is updated to paid is it possible to create a trigger on postgres? they are all in a same table orders
is it possible to create a trigger on postgres
Yes, that's possible.
As documented in the manual you need a trigger function first:
create function update_paid_at()
returns trigger
as
$$
begin
new.paid_at := now();
return new;
end;
$$
language plpglsql;
Then you need a trigger definition:
create trigger update_orders_trigger
before update on orders
for each row
when (new.status = 'paid' and new.status <> old.status)
execute procedure update_paid_at();
The trigger only fires if the status is changed to 'paid' and then sets the value of paid_at column.

Track last modification timestamp of a row in Postgres

In Postgres I want to store table's last update/insert time. Microsoft SQL Server offers a type timestamp which is automatically maintained by the database.
But timestamp in Postgres works differently, it is not updated automatically and the column is always null.
In postgresql, you have to use a trigger. You can follow this link on how to do it https://x-team.com/blog/automatic-timestamps-with-postgresql/ .
To summarize the article you can do the following:
Create the Pl/Pgsql function that will be triggered:
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Create your table
CREATE TABLE mytable (
id SERIAL NOT NULL PRIMARY KEY,
content TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
And finally add the trigger:
CREATE TRIGGER set_timestamp
BEFORE UPDATE ON mytable
FOR EACH ROW
EXECUTE FUNCTION trigger_set_timestamp();
You can find more informations about the question here: https://dba.stackexchange.com/questions/58214/getting-last-modification-date-of-a-postgresql-database-table
Hope it'll help you.
Expanding on SofienM's brilliant answer (and lolung's comment correction), we can also extend this by automatically updating a column with the user name (role) of the row creator, and updating another column with the user name of the last user to modify an existing row. Below is a complete test you can run (I use two imaginary users (user1 & user2) to demonstrate the test):
--create dummy table, execute as user1
CREATE TABLE public.test_table (
id SERIAL PRIMARY KEY,
comments VARCHAR(250),
create_by VARCHAR(250) DEFAULT user,
create_dat TIMESTAMPTZ DEFAULT now(),
modify_by VARCHAR(250) DEFAULT user,
modify_dat TIMESTAMPTZ DEFAULT NOW()
);
--create function trigger to change a timestamp value upon an update
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.modify_dat = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
--create a trigger to execute the function
CREATE TRIGGER set_timestamp
BEFORE UPDATE ON public.test_table
FOR EACH ROW
EXECUTE PROCEDURE trigger_set_timestamp();
--test the created by and created date columns, execute this as user1
INSERT INTO public.test_table (comments)
VALUES ('hello world'),
('hello system'),
('hello universe')
;
--wait a minute or two, execute this query as user1 and observe 'modify_dat' column
UPDATE public.test_table
SET comments = 'hello secret'
WHERE comments = 'hello world';
--create function trigger to change a username value upon an update
CREATE OR REPLACE FUNCTION trigger_set_usertimestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.modify_by = user;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
--create a trigger to execute the function
CREATE TRIGGER set_usertimestamp
BEFORE UPDATE ON public.test_table
FOR EACH ROW
EXECUTE PROCEDURE trigger_set_usertimestamp();
--test the created by and created date columns, execute this as user2 for variation
INSERT INTO public.test_table (comments)
VALUES ('goodbye world'),
('goodbye system'),
('goodbye universe')
;
--wait a minute or two, execute this query as user2 and observe 'modify_by' column
UPDATE public.test_table
SET comments = 'hello unknown'
WHERE comments = 'hello system';
What would improve this is to make the trigger apply to any/every table in a given schema, not just on one specific table (assuming each table has the 'modify_dat' and 'modify_by' columns of course...).

Updating current timestamp of multiple tables through single trigger function

I have 2 tables created :- Table Banks and Branch. Both the tables have column last_updated(which means when was the record last updated.) I have created after update trigger for each row on both the tables. The trigger and trigger function looks like below :-
create trigger banks_upd_trg
after update of phone_no
on Banks
FOR EACH ROW
EXECUTE PROCEDURE bankdetails_upd();
create trigger branch_upd_trg
after update of email_address
on Branch
FOR EACH ROW
EXECUTE PROCEDURE bankdetails_upd();
create or replace FUNCTION bankdetails_upd()
RETURNS trigger AS
$BODY$
BEGIN
EXECUTE format('update %I.%I SET last_updated=current_timestamp where id=new.id',TG_SHEMA_NAME,TG_TABLE_NAME)
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql
The trigger is getting executed successfully but isn't working at the time of updating phone_no and email_address column in Banks and Branch table respectively.
No need for dynamic SQL or an UPDATE statement.
Use a before trigger and assign the value to the NEW record.
create or replace FUNCTION bankdetails_upd()
RETURNS trigger AS
$BODY$
BEGIN
new.last_updated := current_timestamp;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
create trigger banks_upd_trg
BEFORE update of phone_no
on Banks
FOR EACH ROW
EXECUTE PROCEDURE bankdetails_upd();
create trigger branch_upd_trg
BEFORE update of email_address
on Branch
FOR EACH ROW
EXECUTE PROCEDURE bankdetails_upd();

PostgreSQL: Checking for NEW and OLD in a function for a trigger

I want to create a trigger which counts rows and updates a field in an other table. My current solution works for INSERT statements but failes when I DELETE a row.
My current function:
CREATE OR REPLACE FUNCTION update_table_count()
RETURNS trigger AS
$$
DECLARE updatecount INT;
BEGIN
Select count(*) into updatecount
From source_table
Where id = new.id;
Update dest_table set count=updatecount
Where id = new.id;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
The trigger is a pretty basic one, looking like.
CREATE TRIGGER count_trigger
AFTER INSERT OR DELETE
ON source_table
FOR EACH ROW
EXECUTE PROCEDURE update_table_count();
When I excute a DELETE statement the following error occurs:
ERROR: record "new" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
I know one solution could be to create just one set of trigger and function for the DELETE and one for the INSERT statement. But I want to do it a bit more elegant and want to know, if there is a solution to check if NEW or OLD is present in the current context and just implement an IF ELSE block. But I dont know how to check for this context sensitive items.
Thanks for your help
The usual approach to make a trigger function do different things depending on how the trigger was fired is to check the trigger operation through TG_OP
CREATE OR REPLACE FUNCTION update_table_count()
RETURNS trigger AS
$$
DECLARE
updatecount INT;
BEGIN
if tg_op = 'UPDATE' then
select count(*) into updatecount from source_table where id = new.id;
update dest_table set count=updatecount where id = new.id;
elsif tg_op = 'DELETE' then
... do something else
end if;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
Unrelated, but: the language name is an identifier. Do not quote it using single quotes.
From PostgreSQL's documentation:
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is null in statement-level triggers and for DELETE operations.
OLD
Data type RECORD; variable holding the old database row for UPDATE/DELETE operations in row-level triggers. This variable is null in statement-level triggers and for INSERT operations.
So, for example, if NEW is NULL, then the trigger was invoked on DELETE.

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;