Convert time to seconds in PostgreSQL - postgresql

I have a time value 04:30:25 that I want to convert to seconds.
Is there any dedicated function to do this?
I know that we can extract hours, minutes and seconds, then calculate the seconds.
SELECT EXTRACT(hour FROM t)*60*60
+ EXTRACT(minutes FROM t)*60
+ EXTRACT(seconds FROM t)
FROM test;
But I want some other way...

Have you tried using:
SELECT EXTRACT(EPOCH FROM INTERVAL '04:30:25');
If that doesn't work you could try to prefix your time value with '1970-01-01' and try:
SELECT EXTRACT(EPOCH FROM TIMESTAMP '1970-01-01 04:30:25');
Not tested but it seems these are your only options. Probably.

You may skip epoch or interval, ie:
SELECT EXTRACT(EPOCH FROM column ) from table

Perhaps you can make it a function (just a quick setup, please review and change as needed)?
CREATE OR REPLACE FUNCTION to_seconds(t text)
RETURNS integer AS
$BODY$
DECLARE
hs INTEGER;
ms INTEGER;
s INTEGER;
BEGIN
SELECT (EXTRACT( HOUR FROM t::time) * 60*60) INTO hs;
SELECT (EXTRACT (MINUTES FROM t::time) * 60) INTO ms;
SELECT (EXTRACT (SECONDS from t::time)) INTO s;
SELECT (hs + ms + s) INTO s;
RETURN s;
END;
$BODY$
LANGUAGE 'plpgsql';
Then just use it in your queries:
SELECT to_seconds('04:30:25');
Returns:
16225

If you want to emulate MySQL's time_to_sec function, you could use a function like this:
CREATE OR REPLACE FUNCTION time_to_sec(t text)
RETURNS integer AS
$BODY$
DECLARE
s INTEGER;
BEGIN
SELECT (EXTRACT (EPOCH FROM t::interval)) INTO s;
RETURN s;
END;
$BODY$
LANGUAGE 'plpgsql';
It has the advantage that it will work with PostgreSQL intervals (ie: more than 24-hour periods), which would break the to_seconds function in the accepted answer.

As a simplified approach to #hdiogenes solution, just use this in the query:
SELECT EXTRACT (EPOCH FROM '04:30:25'::time)

from_seconds also to convert back
CREATE OR REPLACE FUNCTION from_seconds(t integer)
RETURNS time AS
$BODY$
DECLARE
h INTEGER;
m INTEGER;
s INTEGER;
rv TIME;
BEGIN
SELECT t / 3600 INTO h;
SELECT t % 3600 / 60 INTO m;
SELECT t % 60 INTO s;
SELECT (h::text || ':' || m::text || ':' || s::text)::time INTO rv;
RETURN rv;
END;
$BODY$
LANGUAGE 'plpgsql';

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 can i create n columns in postgresql?

I am trying to write a function that returns random start time (it must be between now and a week long) and an endtime. I wrote this function:
CREATE OR REPLACE FUNCTION random_start_end_time(n integer)
RETURNS TABLE (startime TIMESTAMP WITH TIME ZONE, endtime TIMESTAMP WITH TIME ZONE)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
RETURN QUERY
SELECT NOW() + (random() * (NOW()+'7 days' - NOW())) as startime;
SELECT NOW() + (random() * (NOW()+'7 days' - NOW())) as endtime;
END;
$BODY$
I can't find out how to generate multiple columns. For example I want n=100 columns of random start time and end time to be generated.
In general I can't understand how I can fill an empty table (with this function I am going to fill a table later).
Any thoughts would be valuable.
Thank you.
Use RETURN NEXT to add a row to the result set of a table function and RETURN to end the function execution. You also have to decide if you want a function that returns two columns or two rows. Your case looks like you want to do something like:
CREATE OR REPLACE FUNCTION random_start_end_time(
OUT starttime TIMESTAMP WITH TIME ZONE,
OUT endtime TIMESTAMP WITH TIME ZONE
) RETURNS record
LANGUAGE sql AS
$BODY$
WITH start AS (
SELECT current_timestamp + random() * INTERVAL '7 days' as starttime
)
SELECT starttime,
starttime + random() * INTERVAL '7 days' as endtime
FROM start;
$BODY$;
Call it like
SELECT * FROM random_start_end_time();
If you really want to return several rows, that would be
CREATE OR REPLACE FUNCTION random_start_end_time()
RETURNS SETOF timestamp with time zone
LANGUAGE plpgsql AS
$BODY$
BEGIN
RETURN NEXT current_timestamp + random() * INTERVAL '7 days';
RETURN NEXT current_timestamp + random() * INTERVAL '7 days';
RETURN; /* end the function */
END;
$BODY$;

Using parameters passed to stored procedure in query

I have a sql query where I want to extract records older than 'X' number of days, here for eg its 7 days:
SELECT * FROM BOOKMARK.MONITORING_TABLE WHERE inserteddatetime < (now() - '7 day'::interval);
I have to execute this query through a stored procedure passing in the configurable 'X' no of days as arguments.
The procedure is as below:
CREATE OR REPLACE FUNCTION DELETE_REDUNDANT_RECORDS_STORED_PROCEDURE(days int)
RETURNS void AS
$func$
DECLARE
rec_old RECORD;
cursor_data CURSOR FOR
SELECT * FROM BOOKMARK.MONITORING_TABLE WHERE inserteddatetime < now() - '$1 day'::interval;
BEGIN
OPEN cursor_data;
// business logic for the procedure
CLOSE cursor_data;
END;
$func$
LANGUAGE plpgsql;
This doesn't work as I am not able to use the placeholder for days in my query. How do we use the arguments passed to my query in this case.
To create an interval based on an integer, make_interval() is much easier to use than casting to an interval type.
Additional I wouldn't use a cursor, but a FOR loop based on a SELECT statement (maybe using make_interval(days => $1) works in the cursor declaration as well)
CREATE OR REPLACE FUNCTION DELETE_REDUNDANT_RECORDS_STORED_PROCEDURE(days int)
RETURNS void AS
$func$
DECLARE
rec_old record;
BEGIN
for rec_old in SELECT *
FROM BOOKMARK.MONITORING_TABLE
WHERE inserteddatetime < now() - make_interval(days => $1)
loop
raise notice 'records %', rec_old;
end loop;
END;
$func$
LANGUAGE plpgsql;

How to use argument for table name in dynamic SQL

I am writing a Postgres function to get the number of new records in a table. Here table name is a variable.
create or replace function dmt_mas_updates(
tb_name text,
days integer)
returns integer as
$$
declare
ct integer;
begin
execute 'select count(*) from $1 where etl_create_dtm > now() - $2 * interval ''1 days'' '
using tb_name, days into ct;
return ct;
end;
$$ LANGUAGE 'plpgsql'
When I call the function with select * from dmt_mas_updates('dmt_mas_equip_store_dim',2);, I got syntax error at $1.
If I run the query directly select count(*) from dmt_mas_equip_store_dim where etl_create_dtm >= interval '3 days', it works correctly.
Why am I getting this error? What did I do wrong?
Per the documentation:
Note that parameter symbols can only be used for data values — if you want to use dynamically determined table or column names, you must insert them into the command string textually.
Use the format() function:
create or replace function dmt_mas_updates(
tb_name text,
days integer)
returns integer as
$$
declare
ct integer;
begin
execute format(
'select count(*) from %I where etl_create_dtm > now() - $1 * interval ''1 days'' ',
tb_name)
using days into ct;
return ct;
end;
$$ LANGUAGE 'plpgsql';

Casted timeStamp does not give correct result in PL/pgsql

I'm new to pl/pgsql and I'm trying to execute following function and it gives 0 records as result.
CREATE OR REPLACE FUNCTION public.detectselect (i_channelID integer,te_startTime text,te_endTime text)
RETURNS SETOF detect_inst AS $BODY$
declare
r detect_inst%rowtype;
tstz_endTime timestamp without time zone;
tstz_startTime timestamp without time zone;
BEGIN
tstz_endTime = to_timestamp(te_endTime,'DD/MM/YYYY hh24:mi:ss')::timestamp without time zone;
tstz_startTime = to_timestamp(te_startTime,'DD/MM/YYYY hh24:mi:ss')::timestamp without time zone;
for r in SELECT * FROM detect_inst d WHERE d."ChannelID" = i_channelID AND d."EndTime" >= tstz_startTime AND d."EndTime" < tstz_endTime loop
return next r;
end loop;
RETURN;
END;
$BODY$ LANGUAGE plpgsql;
call as
select detectselect(1,'2016-01-21 0:0:0','2016-01-23 0:0:0');
but it give correct results when i give static time stamp values in this way
CREATE OR REPLACE FUNCTION public.detectselect (i_channelID integer,te_startTime text,te_endTime text)
RETURNS SETOF detect_inst AS $BODY$
declare
r detect_inst%rowtype;
tstz_endTime timestamp without time zone;
tstz_startTime timestamp without time zone;
BEGIN
tstz_endTime = to_timestamp(te_endTime,'DD/MM/YYYY hh24:mi:ss')::timestamp without time zone;
tstz_startTime = to_timestamp(te_startTime,'DD/MM/YYYY hh24:mi:ss')::timestamp without time zone;
for r in SELECT * FROM detect_inst d WHERE d."ChannelID" = i_channelID AND d."EndTime" >= '2016-01-21 0:0:0' AND d."EndTime" < '2016-01-23 0:0:0' loop
return next r;
end loop;
RETURN;
END;
$BODY$ LANGUAGE plpgsql;
Your function is overly complex. You don't need PL/pgSQL and you don't need a (slow) cursor to return the result.
It can be simplified to a plain SQL function which also will be a lot faster:
CREATE OR REPLACE FUNCTION public.detectselect (i_channelID integer, te_startTime timestamp, te_endTime timestamp)
RETURNS SETOF detect_inst AS
$BODY$
SELECT *
FROM detect_inst d
WHERE d."ChannelID" = i_channelID
AND d."EndTime" >= te_starttime
AND d."EndTime" < te_endtime
$BODY$
LANGUAGE sql;
You then call it:
select *
from detectselect(1, timestamp '2016-01-21 00:00:00', timestamp '2016-01-23 00:00:00' );
You should also avoid those dreaded quoted identifiers. They are much more trouble then they are worth it