Postgres: timing the runtime of a function - postgresql

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();

Related

Measure the time it takes to execute a PostgreSQL query

Based on Measure the time it takes to execute a t-sql query, how would one time several trials of a query in PostgreSQL?
A general outline would be
-- set up number of trials (say 1000)
SELECT CURRENT_DATE ; -- save start time
BEGIN
LOOP
-- execute query to be tested
END LOOP;
END;
SELECT CURRENT_DATE ; -- save end time
I.E. I want a PostgreSQL equivalent of the following TSQL code, taken from an answer by HumbleWebDev from the linked TSQL question: see [reference for code]
declare #tTOTAL int = 0
declare #i integer = 0
declare #itrs integer = 100
while #i < #itrs
begin
declare #t0 datetime = GETDATE()
--your query here
declare #t1 datetime = GETDATE()
set #tTotal = #tTotal + DATEDIFF(MICROSECOND,#t0,#t1)
set #i = #i + 1
end
select #tTotal/#itrs
-- your query here: Standard SQL queries such as Select * from table1 inner -- join table2, or executing stored procedure, etc.
Coming from an MSSQL background myself and now more often working in Postgres I feel your pain =)
The "trouble" with Postgres is that it supports only 'basic' SQL commands (SELECT, INSERT, UPDATE, CREATE, ALTER, etc...) but the moment you want to add logic (IF THEN, WHILE, variables, etc.) you need to switch to pl/pgsql which you can only use inside functions (AFAIK). From a TSQL POV there are quite some limitations and in fact, some things suddenly don't work anymore (or need to be done differently.. e.g. SELECT * INTO TEMPORARY TABLE tempTable FROM someTable will not work but CREATE TABLE tempTable AS SELECT * FROM someTable will)
Something I learned the hard way too is that CURRENT_TIMESTAMP (or Now()) will return the same value within a transaction. And since everything inside a function runs inside a transaction this means you have to use clock_timstamp()
Anyway, to answer your question, I think this should get you going:
CREATE OR REPLACE FUNCTION fn_test ( nbrOfIterations int)
RETURNS TABLE (iterations int, totalTime interval, secondsPerIteration int)
AS $$
DECLARE
i int;
startTime TIMESTAMP;
endTime TIMESTAMP;
dummy text;
BEGIN
i := 1;
startTime := clock_timestamp();
WHILE ( i <= nbrOfIterations) LOOP
-- your query here
-- (note: make sure to not return anything or you'll get an error)
-- example:
SELECT pg_sleep INTO dummy FROM pg_sleep(1);
i := i + 1;
END LOOP;
endTime := clock_timestamp();
iterations := nbrOfIterations;
totalTime := (endTime - startTime);
secondsPerIteration := (EXTRACT(EPOCH FROM endTime) - EXTRACT(EPOCH FROM startTime)) / iterations;
RETURN NEXT;
END;
$$ language plpgsql;
SELECT * FROM fn_test(5);
While the accepted answer is correct, this tweaking of it worked better for me. Again, I want to emphasize this extra answer below is based on the above answer, and it would not be possible without it. It just works better in my own situation to use the tweak I made below.
The answer below is indeed almost entirely based on the accepted answer. However, I changed how the return is used and also seconds to milliseconds:
----------------------------------------------------------------------------------------------------
-- fn__myFunction_Q.sql
----------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
-- DROP FUNCTION mySchema.fn__myFunction
--------------------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION mySchema.fn__myFunction ( nbrOfIterations int)
RETURNS TABLE (iterations int, totalTime interval, millisecondsPerIteration int) -- interval --
AS $$
declare
i int;
startTime TIMESTAMP;
endTime TIMESTAMP;
-- dummy text;
iterations int;
millisecondsPerIteration int;
totalTime interval;
BEGIN
i := 1;
startTime := clock_timestamp();
WHILE ( i <= nbrOfIterations) LOOP
PERFORM /* Put your query here, replacing SELECT with PERFORM */
--------------------------------------------------------------------------------------------
--SELECT
-- YOUR QUERY HERE
-- ...
--------------------------------------------------------------------------------------------
i := i + 1; -- very important to increment loop counter, else one gets an infinite loop!!!
END LOOP;
endTime := clock_timestamp();
iterations := nbrOfIterations;
totalTime := (endTime - startTime);
millisecondsPerIteration := 1000 * (EXTRACT(EPOCH FROM endTime) - EXTRACT(EPOCH FROM startTime)) / iterations;
RETURN QUERY select iterations, totalTime, millisecondsPerIteration;
-- RETURNS TABLE (iterations int, totalTime interval, secondsPerIteration int) -- interval --
-- RETURN NEXT;
END;
$$ language plpgsql;
--------------------------------------------------------------------------------------------
To call this function, just use:
SELECT * from mySchema.fn__myFunction(1000) as ourTableResult;

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$

Function (trigger) to insert rows based on interval and duration

I have a schedule table, like so:
ScheduleId::uuid | Start::timestamptz(now()) | SlotSize::int(minutes) | Interval::int(days)
and a slot table like so:
SlotId::uuid | ScheduleId::uuid | Start::timestamptz | End::timestamptz
I want to automatically insert slots, based on a trigger on the schedule table.
So far I have:
create
trigger create_slots after insert
on
schedule for each row execute procedure create_new_slots();
create or replace function create_new_slots()
returns trigger
language plpgsql
as $function$
begin
-- in a loop determine how many slots there are, then insert each one
insert into slot
select
uuid_generate_v4(),
new."ScheduleId",
start, -- need to determine the start time of each instance of slot
end -- need to determine the end time of each instance of slot
-- end loop
end return new;
end $function$
I need to somehow put this into a cursor and calculate the number of slots and the start and end times each slot.
I am using PostgreSQL 10
Any help is appreciated!
OK so what I needed, it seems, was to generate a series based on the [Start] time (rounded to the hour), the end time (which is the [Start] time + the [PlanningHorizon] in days) and the [SlotSize]. Then loop through this series and insert each time slot into my [Slot] table:
CREATE OR REPLACE FUNCTION create_new_slots()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
slot timestamptz;
begin
for slot in select generate_series(
date_trunc('hour', new."Start")::timestamptz,
(new."Start" + interval '1' day * new."PlanningHorizon")::timestamptz,
new."SlotSize" * '1 minutes'::interval)
loop
insert into slot select uuid_generate_v4(), new."ScheduleId", slot, slot + new."SlotSize" * '1 minutes'::interval;
end loop;
return NEW;
end
$function$

Transpose generate series date postgresql

Questions about transpose are asked many times before, but I cannot find any good answer when using generate_series and dates, because the columns may vary.
WITH range AS
(SELECT to_char(generate_series('2015-01-01','2015-01-05', interval '1 day'),'YYYY-MM-DD'))
SELECT * FROM range;
The normal output from generate series is:
2015-12-01
2015-12-02
2015-12-03
... and so on
http://sqlfiddle.com/#!15/9eecb7db59d16c80417c72d1e1f4fbf1/5478
But I want it to be columns instead
2015-12-01 2015-12-02 2015-12-03 ...and so on
It seems that crosstab maybe should do the trick, but I only get errors:
select * from crosstab('(SELECT to_char(generate_series('2015-01-01','2015-01-05', interval '1 day'),'YYYY-MM-DD'))')
as ct (dynamic columns?)
How do I get crosstab to work with generate_series(date-date) and different intervals dynamically?
TIA
Taking Reference from link PostgreSQL query with generated columns.
you can generate columns dynamically:
create or replace function sp_test()
returns void as
$$
declare cases character varying;
declare sql_statement text;
begin
drop table if exists temp_series;
create temporary table temp_series as
SELECT to_char(generate_series('2015-01-01','2015-01-02', interval '1 day'),'YYYY-MM-DD') as series;
select string_agg(concat('max(case when t1.series=','''',series,'''',' then t1.series else ''0000-00-00'' end) as ','"', series,'"'),',') into cases from temp_series;
drop table if exists temp_data;
sql_statement=concat('create temporary table temp_data as select ',cases ,'
from temp_series t1');
raise notice '%',sql_statement;
execute sql_statement;
end;
$$
language 'plpgsql';
Call function in following way to get output:
select sp_test(); select * from temp_data;
Updated Function which takes two date paramaeters:
create or replace function sp_test(start_date timestamp without time zone,end_date timestamp without time zone)
returns void as
$$
declare cases character varying;
declare sql_statement text;
begin
drop table if exists temp_series;
create temporary table temp_series as
SELECT to_char(generate_series(start_date,end_date, interval '1 day'),'YYYY-MM-DD') as series;
select string_agg(concat('max(case when t1.series=','''',series,'''',' then t1.series else ''0000-00-00'' end) as ','"', series,'"'),',') into cases from temp_series;
drop table if exists temp_data;
sql_statement=concat('create temporary table temp_data as select ',cases ,'
from temp_series t1');
raise notice '%',sql_statement;
execute sql_statement;
end;
$$
language 'plpgsql';
Function call:
select sp_test('2015-01-01','2015-01-10'); select * from temp_data;

Error while trying to insert data into timestamp field using plpgsql

I have the following plpgsql function:
CREATE OR REPLACE FUNCTION test_func(OUT pid bigint)
RETURNS bigint AS
$BODY$
DECLARE
current_time timestamp with time zone = now();
BEGIN
INSERT INTO "TEST"(
created)
VALUES (current_time) RETURNING id INTO pid;
END
$BODY$
LANGUAGE plpgsql;
select * from test_func();
The above gives an error:
column "created" is of type timestamp with time zone but expression is of type time with time zone
Insertion query without function:
INSERT INTO "TEST"(
created)
VALUES (now()) RETURNING id INTO pid;
or if now() is used directly without defining variable it works.
CURRENT_TIME is a reserved word (and a special function), you cannot use it as variable name. You don't need a variable here to begin with:
CREATE OR REPLACE FUNCTION test_func(OUT pid bigint) AS
$func$
BEGIN
INSERT INTO "TEST"(created)
VALUES (now())
RETURNING id
INTO pid;
END
$func$
LANGUAGE plpgsql;
now() is a STABLE function. It does not change across the same transaction. There is no need to capture the result into a variable.
How do IMMUTABLE, STABLE and VOLATILE keywords effect behaviour of function?