I am trying to reproduce example from https://www.alibabacloud.com/blog/598021.
According to that example, the following should work (proven by the c/p of the output there).
create or replace function im_now () returns timestamptz as $$
select CURRENT_TIMESTAMP;
$$ language sql strict immutable;
create table t1 (id int primary key, info text, crt_time timestamp,
mod_time timestamp GENERATED ALWAYS AS (im_now()) stored);
insert into t1 (id, info, crt_time) values (1,'test', now());
select * from t1;
update t1 set info='a' where id=1;
select * from t1;
But when I try that, mod_time is set when creating new record and not changed after the update.
No errors are reported. I am using PostgreSQL version 15.2.
What might be wrong?
2
Answers
Currently mod_time will only change when the record is created. There is not a single column mentioned in the function im_now() that could behave as a trigger to change this value for mod_time.
With two minor changes, you can fix this issue:
The column "info" will now behave as a trigger to create a new timestamp for the column "mod_time".
The article uses a trick as it incorrectly, but on purpose, declares the function as
immutable
.Without any paramater, an immutable function only ever returns 1 value; based on the information it is provided, the optimizer decides it does not need to call the function again as its result will be the same as what it had before.
If you add a parameter to the
im_now()
function, then everything changes: the function is still declaredimmutable
but its result now depends on some input. The optimizer cannot optimize the call away without first determining that none of the input parameters are being changed. More about that after the code sample.The below example demonstrates how to update
mod_time
when changingid
orinfo
, by declaringmod_time
depends onid
andinfo
, but not when changingcrt_time
ascrt_time
is not a parameter in the call.In a more realistic test case, I suppose you would want to declare
crt_time
asGENERATED
too, to prevent people from updating it.Additional details about the optimizer for your query
This section is unnecessary if you wish to only copy the sample above and not understand all the kinks behind it.
But to be perfectly complete, let us analyze the situations where
im_now
is called, where it is not called and what it may mean if the optimizer is improved in future versions of Postgres.In the version of Postgres I use to test:
im_now(id::text, info)
is NOT called when neitherid
notinfo
are being updatedSee scenario 1.
In the above example, all the columns that you can effectively update (I am not counting
crt_time
/mod_time
since you cannot force a value in them, and updating withcrt_time = default
ormod_time = default
does nothing).im_now(id::text, info)
is called when modifying the value ofid
orinfo
.See scenario 2.
im_now(id::text, info)
is called when updatingid
orinfo
with their current value (i.e. the value does not change during the update).See scenarios 3 and 4.
For scenarios 3 and 4, we can argue whether not modifying the record requires
mod_time
to be refreshed. This is up to every programmer’s design.The fact is, in the version of Postgres I am using, it is refreshed (i.e. the call to
im_now(id::text, info)
is not optimized away).This has some consequences:
This may change in a future version of Postgres, or this may be different for other DBMS based on Postgres.
I recommend you do not create a strong requirement that
mod_time
should/should not be refreshed in scenarios 3 and 4 otherwise it may (will?) break down in the future.If a cache is ever implemented, the optimizer may be able to optimize calls away across several queries.
Let us imagine for a minute I declared the column not to use
id::text
:Then, it is theoretically possible to have:
This is why I have put the primary key
id
as a parameter ofim_now
in the able example, so that even in the future, a record will not be able to use the result cached by the call toim_now
for another record, at another time.