0

I am new to Postgresql and SQL in general. I have a table that looks like this

id (primary key)|temperature|temperature_timestamp|humidity|humidity_timestamp|...|parameter|parameter_timestamp 

I want the timestamp of a particular "parameter"_timestamp to be automatically updated when I do an 'UPDATE' on a particular parameter.

Eg: when I update the temperature in a particular row the corresponding temperature_timestamp on that row should update

I am following this link currently https://www.revsys.com/tidbits/automatically-updating-a-timestamp-column-in-postgresql/ and am able to update one column how do I extend this to my entire table ?

MY Trigger funtion looks like this :

CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.temp_lastupdated = now();
RETURN NEW;
END;
$$ language 'plpgsql'; 

And my trigger looks like this

CREATE TRIGGER update_timestamp
BEFORE UPDATE
ON sensor_data
FOR EACH ROW EXECUTE PROCEDURE  update_timestamp();
2
  • What exactly do you mean with "extend this to the entire table"? Do you want to update all rows in the table, just because one was changed?
    – user1822
    Commented Mar 17, 2020 at 14:46
  • @a_horse_with_no_name no i am using postgres-12 (appologies). i mean when i update temperature the temperature_timestamp should update automatically, when i update humidity, the humidity_timestamp should update and so on Commented Mar 17, 2020 at 14:48

2 Answers 2

1

You can check which parameters has changes in a single trigger:

create or replace function trg_sensor_data()
returns trigger as 
$$
begin

  if NEW.p1 <> OLD.p1 and NEW.p1 IS NOT NULL then
      NEW.p1_tm = now();
  end if;

  if NEW.p2 <> OLD.p2 and NEW.p2 IS NOT NULL  then
      NEW.p2_tm = now();
  end if;

  return NEW;

end;
$$
language 'plpgsql';
update sensor_data set p1 = 100;

select * from sensor_data;
1 rows affected

id |  p1 | p1_tm                      | p2 | p2_tm              
-: | --: | :------------------------- | -: | :------------------
 1 | 100 | 2020-03-17 15:18:36.220044 | 23 | 2020-01-01 11:00:00
update sensor_data set p2 = 200;

select * from sensor_data;
1 rows affected

id |  p1 | p1_tm                      |  p2 | p2_tm                     
-: | --: | :------------------------- | --: | :-------------------------
 1 | 100 | 2020-03-17 15:18:36.220044 | 200 | 2020-03-17 15:18:36.223565

db<>fiddle here

1
1

For my preference, a trigger is the wrong tool for the job here. Trying to outguess the application in terms of whether it really meant to update a certain value or not is a losing game. Let the business logic do what it is told and if it updates value "a", then you as the administrator require that it also update "a_timestamp" part and parcel of the same transaction.

By way of example: if you're limited in your ability to modify the app code but you can require a json payload, you can use a model like the following (full dbfiddle here).

Given a table of the form...

create table foo (
    id serial primary key,
    a int,
    b int,
    c int,
    a_tstz timestamptz not null default now(),
    b_tstz timestamptz not null default now(),
    c_tstz timestamptz not null default now()
);
insert into foo (a,b,c)
values
(1,2,3),
(4,5,6);

...and a proc of the form...

create procedure bar (payload jsonb)
language sql
as $$
    with j as (
       select payload as p
    ), cte as (
        select 
            (p->>'id')::int as id,
            case when p ? 'a' then (p->>'a')::int else f.a end as a,
            case when p ? 'b' then (p->>'b')::int else f.b end as b,
            case when p ? 'c' then (p->>'c')::int else f.c end as c,
            case when p ? 'a' then now()::timestamptz else f.a_tstz end as a_tstz,
            case when p ? 'b' then now()::timestamptz else f.b_tstz end as b_tstz,
            case when p ? 'c' then now()::timestamptz else f.c_tstz end as c_tstz
        from foo f
        join j on (j.p->>'id')::int = f.id 
    )
    update foo as f set
        a = c.a,
        b = c.b,
        c = c.c,
        a_tstz = c.a_tstz,
        b_tstz = c.b_tstz,
        c_tstz = c.c_tstz
    from cte as c
    where c.id = f.id;
$$

...you can call bar('{"id":1,"a":1,"c":null}'::jsonb);. This will demonstrate to you that attributes which are specified in the json payload are updated alongside their paired timestamps while unspecified attributes are not updated. This is true even if specified attributes

  1. Are updated to the same value as they were before
  2. Are moved to an unknown NULL state

Updates that occur outside usage of the procedure with a valid jsonb payload will still require that you update the paired timestamp "manually". The proc demonstrates only that you can infer which timestamps need to be updated. It's worth noting that this approach as-written here likely has a bunch of edge case bugs you'd want to QA for. Off the top of my head...

  1. malformed payload - no id supplied, a supplied multiple times, invalid data supplied, etc...
  2. multiple payload - I only tested a single valid payload. If you supply multiple payloads at the same time what do you want to happen.
  3. upsert logic - the proc as given only updates values. How will you detect no update has happened and insert as needed?

Possibly also worth noting is that requiring yourself to update the data/timestamp pairs via the procedural code allows you to do out-of-band updates to data in the table that don't futz the timestamp without disabling the trigger. While you may say now "I always want the timestamp updated.", in my experience it's a questionable choice to paint yourself into a corner like that. To put it another way: "It's not a bug, it's a feature."

2
  • I still have some issues with my implementation here is a link to dbfiddle.uk/… How do i automatically update the time stamp here with my last update command ? @Peter Commented Mar 17, 2020 at 21:26
  • updates that occur outside usage of the procedure with a valid jsonb payload will still require that you update the timestamp "manually". The proc is only a demonstration that you can infer which timestamps need to be updated in logic. See this modification to your demo dbfiddle.uk/… . It's worth noting that this approach as-written likely has a bunch of edge-case bugs you'll want to QA for (malformed payload, multiple payload off the top of my head) Commented Mar 18, 2020 at 6:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.