add interval to timestamp on record creation trigger - postgresql

I am trying to do what on the surface seems a simple task. I have a table with the fields expires_at (timestamp) ttl (integer) and name (text)
the ttl is supplied as 86400. I want to set the expires_at field to be the current timestamp + the ttl.
this is my trigger
CREATE FUNCTION public.set_expires() RETURNS trigger
LANGUAGE plpgsql
AS $_$
BEGIN
NEW.expires_at := CURRENT_TIMESTAMP + (NEW.ttl || ' seconds')::INTERVAL;
RETURN NEW;
END;
$_$;
however, when this runs, I get the current time in the expires_at field.
if I change the trigger to be
NEW.expires_at := CURRENT_TIMESTAMP + (86400 || ' seconds')::INTERVAL;
then the expires_at is set correctly.
This would imply that NEW.ttl is not set, but if I add
NEW.name:=NEW.ttl;
to the trigger, name is set to 86400. So I am somewhat confused about what I am doing wrong here and would appreciate some help ;)

Althought your version should work (as #NickBarnes shows in the fiddle), maybe you could try to change the syntax to
NEW.expires_at := CURRENT_TIMESTAMP + NEW.ttl * interval '1 second';

Related

How to compare timestamp with integer in Postgres?

I want to have a trigger that would remove existing records where they have lived for more then 10 mins
drop table if exists authorization_code;
create table if not exists authorization_code(
id int generated always as identity,
created_at timestamptz not null
);
drop function if exists remove_expired();
create function remove_expired()
returns trigger
language plpgsql
as $$
begin
--NOTE 10 mins as recommended by the OAuth2 spec
delete from authorization_code where now() - created_at > 600;
return NEW;
end;
$$;
drop trigger if exists clean_up_expired_code on authorization_code;
create trigger clean_up_expired_code
before insert
on authorization_code
for each row
execute procedure remove_expired();
But right now if I insert, I would get an error like this:
sso=# insert into authorization_code(created_at) values(now());
ERROR: operator does not exist: interval > integer
LINE 1: ...lete from authorization_code where now() - created_at > 600
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
QUERY: delete from authorization_code where now() - created_at > 600
CONTEXT: PL/pgSQL function remove_expired() line 4 at SQL statement
What is the correct way to achieve what I want?
The result of subtracting two timestamps is an interval so you can directly compare that:
where now() - created_at > interval '10 minutes';
Or if you want to provide duration as e.g. a parameter indicating the number of seconds:
where now() - created_at > make_interval(secs => 600);
Try using this modified version of the remove_expired() function
halley=> \sf remove_expired
CREATE OR REPLACE FUNCTION public.remove_expired()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
--NOTE 10 mins as recommended by the OAuth2 spec
delete from authorization_code where EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)-EXTRACT(EPOCH FROM created_at) > 600;
return NEW;
end;
$function$

How to acheive a "stolen_at" field which is populated when the "status" field is updated? TRIGGER function and GENERATED ALWAYS AS attempts attached

How to update stolen_at such that it is populated with the current timestamp when status is updated from INSTOCK to STOLEN?
I've attempted a trigger based approached but it contains a bug:
SQL Error [2F005]: ERROR: control reached end of trigger procedure without RETURN
Where: PL/pgSQL function stolen()
The trigger approach:
CREATE TABLE items (
id INTEGER PRIMARY KEY,
item_name text NOT NULL,
item_status text NOT NULL,
stolen_at TIMESTAMP WITHOUT TIME zone
);
CREATE OR REPLACE FUNCTION stolen() RETURNS TRIGGER AS $CODE$
BEGIN
-- Populate stolen_at with datetime only if item_status
-- changes from INSTOCK to STOLEN
IF NEW.item_status = 'STOLEN'
AND OLD.item_status = 'INSTOCK'
THEN
NEW.stolen_at : = now() AT TIME zone 'utc';
END IF;
END $CODE$ LANGUAGE plpgsql;
CREATE TRIGGER populate_stoken
AFTER INSERT OR UPDATE OF item_status
ON items FOR EACH ROW EXECUTE PROCEDURE stolen();
The other approach explored was a function which takes in the id and new_item_status and populates the item_status field. I could not get it to work though:
CREATE TABLE items (
id INTEGER PRIMARY KEY,
item_name text NOT NULL,
item_status text NOT NULL,
stolen_at TIMESTAMP WITHOUT TIME zone NOT NULL GENERATED ALWAYS AS (stolen2(id, item_status)) STORED
);
CREATE OR REPLACE FUNCTION stolen2(id INT, new_item_status text) RETURNS TIMESTAMP AS $CODE$
DECLARE stolen_at TIMESTAMP WITHOUT TIME zone;
BEGIN
SELECT
*
FROM
items
WHERE
id = id
AND item_status = 'INSTOCK';
-- Return if conditions satisfy otherwise return null
IF found AND new_item_status = 'STOLEN'
THEN
RETURN now() AT TIME zone 'utc';
ELSE
RETURN NULL;
END IF;
END $CODE$ LANGUAGE plpgsql;
Which of the two approaches is preferred (e.g. less resource intensive) and what is the correct way to implement them?
Your first trigger is almost fine, but two things are wrong:
it has to be a BEFORE trigger so that you can modify the new row
you are missing RETURN NEW; right before the final END; (see here for a discussion)

Postgres: timing the runtime of a function

I want to update an audit table that stores the duration of a function/stored proc,
so far I have
drop table if exists tmp_interval_test;
create table tmp_interval_test (
id serial primary key,
duration interval
);
drop function if exists tmp_interval;
create or replace function tmp_interval()
returns void as
$body$
declare
sleep int;
start_time timestamp;
end_time timestamp;
diff interval;
begin
start_time := now();
sleep := floor(random() * 10 + 1)::int;
-- actual code goes here
perform pg_sleep(sleep);
end_time := now();
diff := age(end_time, start_time);
insert into tmp_interval_test (duration) values (diff);
end;
$body$
language 'plpgsql' volatile;
However, when I test this function, the duration shows
id|duration|
--|--------|
1|00:00:00|
How do I correctly insert the duration into my table?
The now() functions returns transaction time - it is same inside one transaction. So 0 is correct result. You should to use different functions, that returns real time - Use clock_timestamp() function instead.
On second hand, if you want to collect times of functions, you can use a buildin functionality in Postgres (if has superuser rights). Activate tracking functions. Then you can see what you need in system table pg_stat_user_function.
See SO regarding now()
Updated function and used clock_timestamp() instead of now(), e.g.,
start_time := clock_timestamp();

PostgreSQL AFTER INSERT trigger prevents insert

I have a PostgreSQL 9.3 database. I use a log4net configuration to insert the errors in a table: log_messages.
I have a webpage that shows the errors in a nice way with charts and such.
Because I use a rather complex view this webpage is very slow, so I moved to a materialized view. My page is fast again.
Now I need to keep my materialized view in sync with my table/view. So I created a AFTER INSERT trigger on the table:
CREATE TRIGGER refresh_mv_insert
AFTER INSERT
ON log_messages
FOR EACH ROW
EXECUTE PROCEDURE refresh_mv();
My refresh_mv() is more complicated but even this simplified version doesn't work:
CREATE OR REPLACE FUNCTION refresh_mv()
RETURNS trigger AS
$BODY$
DECLARE
l_view_name character varying := 'mv_log_messages';
begin
EXECUTE 'REFRESH MATERIALIZED VIEW ' || l_view_name;
RETURN NEW;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
When I change it to an anonymous procedure the full and simplified version do work. So it seems I have an error in the Trigger part.
I've been reading documentation and similar Q&A for two days now but I can't get it to work.
Any help is much appreciated.
Edit: Clarification
In my full trigger procedure I use a config table to store the refresh timestamp and I don't refresh within an hour.
When I enable the trigger the record is not saved into the log_messages table.
I don't know how to read any trigger errors. Where can I find them?
Here's my full code:
CREATE OR REPLACE FUNCTION refresh_mv()
RETURNS trigger AS
$BODY$
DECLARE
l_last_refresh timestamp;
l_view_name character varying := 'mv_log_messages';
l_num_new smallint;
l_refresh boolean := false;
begin
l_refresh := false;
-- check the last time:
select last_refresh, num_new into l_last_refresh, l_num_new from config where view_name = l_view_name;
-- refresh every hour
if (l_last_refresh + interval '1 hour' < current_timestamp) then
l_refresh := true;
end if;
-- refresh every 10 inserts, but not more often than every 10 minutes:
if (l_num_new > 9 and l_last_refresh + interval '10 minutes' < current_timestamp) then
l_refresh := true;
end if;
if l_refresh then
-- Reset config and do refresh:
update config set last_refresh = current_timestamp, num_new = 0 where view_name = l_view_name;
-- this line prevents the insertion of the record EXECUTE 'REFRESH MATERIALIZED VIEW ' || l_view_name;
else
-- Update counter:
update config set num_new = l_num_new + 1 where view_name = l_view_name;
end if;
RETURN NULL;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
This trigger works because I commented the line EXECUTE 'REFRESH MATERIALIZED VIEW ' || l_view_name;

PGSQL: Adding a variable interval - Syntax error

I have a quick question. I am fairly new to pgsql and I am unable to figure out how to fix the syntax error below.
Here is what I am trying to do
start_date := '2011-01-01'::date;
end_date := '2011-03-01'::date;
duration := '6 months'
while start_date < end_date loop
window_start_date = start_date;
window_end_date = window_start_date + interval||duration||;
end loop;
However I keep getting a syntax error.
ERROR: column "interval" does not exist
LINE 1: SELECT $1 + interval|| $2 ||
^
QUERY: SELECT $1 + interval|| $2 ||
What am I doing wrong. Any help would be much appreciated
Guesswork (the rest of the function definition is missing).
This would work in PL/pgSQL (which are using behind the curtains):
window_end_date := window_start_date + interval duration;
Or:
window_end_date := window_start_date + duration::interval;
Cast the text value to interval to make it work. But it would be better to declare the variable duration as interval to begin with (maybe that is the case, then drop the cast - information missing).
The assignment operator in plpgsql is :=, not =.
The result is a timestamp, not a date. But it will be coerced to date in your example.