Updating timestamp when a field is edited in pg admin - postgresql

I am modifying data directly in pg admin 4, where I have to validate manually by marking a boolean value to true that is false by default.
I want that when I modify the value in that column, the updated_at column value should also be updated to current timestamp so I can query data with modified date.
How do I achieve this?

The post desired behavior is more simple than code demo in https://www.postgresql.org/docs/current/plpgsql-trigger.html.
setup:
CREATE temp TABLE test (
misc int,
is_valid boolean,
updated_at timestamptz
);
INSERT INTO test (misc, is_valid)
VALUES (1, FALSE);
CREATE FUNCTION update_test_update_at() RETURNS trigger AS $func$
BEGIN
IF NEW.* != OLD.* THEN NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER update_last_edit
BEFORE UPDATE OF is_valid ON test
FOR EACH ROW
EXECUTE FUNCTION update_test_update_at();
test:
UPDATE
test
SET
is_valid = true
RETURNING
*;

Related

PostgreSQL - Trigger on INSERT or UPDATE

I wan't to add a trigger in a PostgreSQL database. This trigger is used to concatenate values of 2 columns to update a 3rd one. I wan't to run it when a row is inserted or updated in the table.
Table
CREATE TABLE IF NOT EXISTS FILE(ID INT NOT NULL PRIMARY KEY, FOLDER TEXT, NAME TEXT, URL TEXT);
Function
CREATE OR REPLACE FUNCTION LINK() RETURNS trigger AS
$$
BEGIN
IF (TG_OP = 'UPDATE') THEN
UPDATE FILE
SET URL = CONCAT(FOLDER, NAME)
WHERE ID = OLD.ID;
ELSIF (TG_OP = 'INSERT') THEN
UPDATE FILE
SET URL = CONCAT(FOLDER, NAME)
WHERE ID = NEW.ID;
END IF;
RETURN NULL;
END
$$
LANGUAGE PLPGSQL;
Trigger
CREATE TRIGGER TRIGGER_LINK
BEFORE INSERT OR UPDATE
ON FILE
FOR EACH ROW
EXECUTE PROCEDURE LINK();
When I insert a value in table like
INSERT INTO FILE VALUES (1, 'C:\', 'doc.pdf');
I have an error message list index out of range because ID number is not yet created and UPDATE query on INSERT can't execute. But if I make an AFTER UPDATE it will run infinitely.
How to run a trigger function on INSERT or UPDATE with a WHERE clause on ID to target only inserted or updated row ? I'm using PostgreSQL 10.14.
Don't use UPDATE to do that. Just assign the value. Also, a BEFORE trigger should not return null because that will abort the operation.
CREATE OR REPLACE FUNCTION LINK() RETURNS trigger AS
$$
BEGIN
new.url := CONCAT(new.FOLDER, new.NAME);
RETURN new; --<< important!
END
$$
LANGUAGE PLPGSQL;

Interpreting TG_TABLE_NAME as a value to update row in other table

I want to update a row in the master_table_info table with the latest timestamp whenever certain other tables are updated. Each row in the table corresponds to another table. I've created this function, but I cannot get TG_TABLE_NAME to be interpreted as a variable value and not a new column. I thus get the error column some_table does not exist. How do I interpret it as a value?
CREATE OR REPLACE FUNCTION master_table_timestamp()
RETURNS TRIGGER AS
$$
BEGIN
EXECUTE format('
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = %I', TG_TABLE_NAME);
RETURN NULL;
END;
$$
language plpgsql;
CREATE TRIGGER master_table_timestamp
BEFORE UPDATE ON some_table
EXECUTE PROCEDURE master_table_timestamp();
EDIT
Based on the answer/comments so far and reading up the trigger documentation, I realized that I should use TG_TABLE_NAME and change to an AFTER trigger. However, modifying the table with the trigger produces no changes on master_table_info. What could be wrong?
CREATE OR REPLACE FUNCTION master_table_timestamp()
RETURNS TRIGGER AS
$$
BEGIN
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = TG_TABLE_NAME;
RETURN new;
END;
$$
language plpgsql;
CREATE TRIGGER master_table_timestamp
AFTER UPDATE ON some_table
EXECUTE PROCEDURE master_table_timestamp();
2nd Edit
This code in my edit above (based on assistance from the answers) is correct. I just needed to force a manual refresh of the table for it to correctly show.
%I replaces the placeholder as an identifier. So the generated SQL would be
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = some_table;
To replace a literal value you would need %L as a placeholder in the String. The %L placeholder takes care of properly quoting values, so if you use that, the generated string would be:
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = 'some_table';
which is what you expected.
However there is no need for dynamic SQL to begin with. As you use that function in a before trigger it is important that you return a non-null value from it, otherwise the UPDATE statement would be cancelled.
CREATE OR REPLACE FUNCTION master_table_timestamp()
RETURNS TRIGGER AS
$$
BEGIN
UPDATE master_table_info
SET updated_at = NOW()
WHERE table_name = TG_TABLE_NAME;
RETURN new; --<< return a NON-NULL value here!
END;
$$
language plpgsql;

Postgresql function return trigger

I am having a problem with a trigger. I created a trigger and a function
for when performing an INSERT update a field in the same table.
Is returning:
Error: function "loss_func" in FROM has return type trigger that is
not supported LINE 1: SELECT * FROM table.loss_func ()
Function
CREATE OR REPLACE FUNCTION loss_func()
RETURNS trigger AS $loss_func$
BEGIN
NEW.dt_creation := to_char(now(), 'YYYY-MM-DD');
RETURN NULL;
END;
$loss_func$ LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION loss_func()
OWNER TO postgres;
Trigger
CREATE TRIGGER tgr_loss
AFTER INSERT ON loss
FOR EACH ROW
EXECUTE PROCEDURE loss_func();
What am I doing wrong?
A working version of your code.
- The trigger now fires BEFORE insert and updates the value of dt_creation and returns the NEW version of the record :
drop table loss;
create table loss (
id int ,
dt_created varchar);
CREATE OR REPLACE FUNCTION loss_func()
RETURNS trigger AS $loss_func$
BEGIN
NEW.dt_created := to_char(now(), 'YYYY-MM-DD');
RETURN NEW;
END;
$loss_func$ LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION loss_func()
OWNER TO postgres;
CREATE TRIGGER tgr_loss
BEFORE INSERT ON loss
FOR EACH ROW
EXECUTE PROCEDURE loss_func();
insert into loss(id) values(1);
Another solution that i can propose to avoid the usage of a trigger is to use a default value for dt_creation when you create the table (and use timestamp instead of storing the date as varchar) :
...
dt_creation timestamp default now(),
...
or you can alter your table to set the default value to now() :
alter table loss
alter column dt_creation set default now();

How to modify Trigger to update a single attribute in PostgreSQL

Here is my sample table.
CREATE TABLE employee_test(
idTst SERIAL PRIMARY KEY,
monthDownload VARCHAR(6),
changeDate DATE);
I am trying to create a function and trigger that would update changeDate attribute with a current date when monthDownload attribute is updated.
The function I have it works with one problem. It updates all records instead of the one that was updated.
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where OLD.idTst = NEW.idTst;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
Trigger
Create TRIGGER dataTest
AFTER UPDATE
ON employee_test
FOR EACH ROW
EXECUTE PROCEDURE downloadMonthChange();
When I execute the following Update statement:
UPDATE employee_test SET monthDownload = 'oct12'
WHERE idTst = 1;
All changeDate rows get update with a current date.
Is there a way to have only a row with changed record to have a current date updated.
If you use a before trigger you can write directly to NEW
CREATE OR REPLACE FUNCTION downloadMonthChange()
RETURNS TRIGGER AS
$$
BEGIN
IF NEW.monthDownload <> OLD.monthDownload THEN
NEW.changeDate = current_date;
END IF;
RETURN NEW;
END;
$$
Language plpgsql;
the other option when you must use an after trigger is to include the primary key in the where clause. It appears that you were trying to do this, but you had a spurious OLD in the query. beause of that the where clause was only looking at the record responsible for the trigger call, and not limiting which records were to be updated.
IF NEW.monthDownload <> OLD.monthDownload THEN
UPDATE employee_test
SET changeDate = current_date
where idTst = NEW.idTst;

PostgreSQL: how to fetch previous insert timestamp

Suppose that I have table
create table foo (
insert_current timestamp default now(),
insert_previous timestamp,
bar int primary key,
baz varchar(10)
);
The data will be replaced now and then when fresh batch arrived. I would like to keep track on when previous batch was inserted as well as current timestamp. What would be a good way to do this?
I would create a trigger that automatically sets insert_current and insert_previous on UPDATE statements.
First, create the trigger function:
CREATE OR REPLACE FUNCTION do_update() RETURNS "trigger"
AS $$
BEGIN
NEW.insert_previous := OLD.insert_current;
NEW.insert_current := NOW();
return NEW;
END;
$$
LANGUAGE plpgsql;
Then add the trigger to your table:
CREATE TRIGGER do_update
BEFORE UPDATE ON foo
FOR EACH ROW
EXECUTE PROCEDURE do_update();
When you update a row, copy the value of insert_current to insert_previous and set insert_current to the current timestamp:
UPDATE foo
SET baz = 'whatever',
insert_previous = insert_current,
insert_current = NOW()
WHERE bar = 1;