postgresql date_trunc to arbitrary precision? - postgresql

postgresql has date_trunc that can truncate the time stamp value to a specific unit, like hour or minute. I want to know if there's any build-in function that would allow me to truncate to 10 minutes?
I know one trick is to convert the time stamp to epoch, do some math, then convert back. But I don't like it.

There is no function you want, but as said in postgresql wiki you can define function for youself:
CREATE OR REPLACE FUNCTION round_time_10m(TIMESTAMP WITH TIME ZONE)
RETURNS TIMESTAMP WITH TIME ZONE AS $$
SELECT date_trunc('hour', $1) + INTERVAL '10 min' * ROUND(date_part('minute', $1) / 10.0)
$$ LANGUAGE SQL;
Generally rounding up to $2 minutes:
CREATE OR REPLACE FUNCTION round_time_nm(TIMESTAMP WITH TIME ZONE, INTEGER)
RETURNS TIMESTAMP WITH TIME ZONE AS $$
SELECT date_trunc('hour', $1) + ($2 || ' min')::INTERVAL * ROUND(date_part('minute', $1) / $2)
$$ LANGUAGE SQL;

here's an improved version of date_trunc
create cast (bigint as timestamptz) WITHOUT FUNCTION;
create cast (timestamptz as bigint) WITHOUT FUNCTION;
CREATE OR REPLACE FUNCTION date_trunc_by_interval( interval, timestamptz )
RETURNS timestamptz
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT
AS $$
select
case when $2::bigint >= 0::bigint then
$2::bigint - $2::bigint % (extract (epoch from $1)*1000000 ) ::bigint
else
$2::bigint - $2::bigint % (extract (epoch from $1)*1000000 ) ::bigint
- (extract (epoch from $1)*1000000 ) ::bigint
end ::timestamptz
$$;
this allows rounding to any fixed-length interval eg: '864 seconds' (divinding days into 100 parts) or '14 days' dividing the calendar into fortnights. the basis is '2000-01-01 00:00:00.0 +00' which is the epoch used to compute postgres
timestamp values.
it works by coercing the timestamptz value and the interval into bigints and doing integer arithmetic on them then coercing them back to timestamps
negative inputs need special handling (the case statement) as % causes rounding towards zero.

Postgres 14 date_bin.
Example use
SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-01-01');
Result: 2020-02-11 15:30:00
The timescaleDb extension has a time_bucket function that supports day, minutes and lower intervals.
Note: it does currently not support months, years: see #414

Related

PostgreSQL - Reference a variable value in select statement interval

I'm trying to calculate a value in a variable then use that value to drive a interval statement further on.
Running on PG 9.6.
We can get to PG 13 if there is something added since then we can use.
Example:
CREATE OR REPLACE FUNCTION public.demofunc(
int,
int,
date)
returns date
LANGUAGE plpgsql AS $$
DECLARE
diffvalue text;
returndate date;
BEGIN
diffvalue:= ($2 - $1);
returndate := (SELECT $3 - INTERVAL 'diffvalue days');
return returndate;
END$$;
This is a simplified version of what I want to achieve, the interval of the numbers of days to remove is based on a calculation that's done in the function. So its not a simple A - B but the end result is
I can't seem to get the function to resolve the "diffvalue" before running the select statement. I've tried using int and text and varchar and concat the string for anything.
Sorry for anything obvious i might be missing, only started this today.
If your diffvalue is in reality an integer, you can use the make_interval function to create an interval based on that number:
returndate := $3 + make_interval(days => diffvalue);
If diffvalue is a decimal that can represent fractional days, make_interval can't be used.
In that case you can multiply an interval of 1 day with that value:
returndate := $3 + interval '1 day' * diffvalue;

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$;

How to extract miliseconds from timestamp dynamically in postgres plpgsql?

I want to get milliseconds from a 'Timestamp with timezone' using a plsql function.
I am able to generate the following function, but it is leading to truncation of miliseconds.
CREATE or REPLACE FUNCTION getMSFromTime(t1 timestamp with time zone)
RETURNS bigint AS $$
declare time1 double precision ;
BEGIN
SELECT EXTRACT(EPOCH FROM t1) into time1;
return time1;
END;
$$ LANGUAGE plpgsql;
but it ignores miliseconds
SELECT getMSFromTime('2019-02-11 08:01:33.423+00') //1549872093
SELECT getMSFromTime('2019-02-11 08:01:33.000+00') //1549872093
I am able to get a PostgreSQL way so that millisecond decimals are preserved as well, using:
SELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE '2019-02-11 08:01:33.423+00'); // 1549872093.423
But I am not able to integrate it into a function and it gives following error:
syntax error at or near "t1"
LINE 5: ...ELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE t1) into t..
CREATE or REPLACE FUNCTION getMSFromTime2(t1 timestamp with time zone)
RETURNS bigint AS $$
declare time1 double precision ;
BEGIN
SELECT EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE t1) into time1;
return time1;
END;
$$ LANGUAGE plpgsql;
Please suggest a way so as to create a PostgreSQL function which can do this functionality.
extract() returns a double value that represents seconds, by casting that to a bigint you lose the fractional seconds (=milliseconds)
If you want a bigint representing milliseconds, you need to multiple the result with 1000.
There is also no reason to use PL/pgSQL for such a simple thing:
CREATE or REPLACE FUNCTION getmsfromtime(t1 timestamp with time zone)
RETURNS bigint
AS $$
SELECT (EXTRACT(EPOCH FROM t1)*1000)::bigint;
$$
LANGUAGE sql;

POSTGRESQL - calling function with different times as input parameter

I was trying to call the function I created to fetch some data for particular time slot. Given below is a script of my function:
CREATE OR REPLACE FUNCTION my_function(
starttime timestamp with time zone,
endtime timestamp with time zone)
RETURNS TABLE("Deviceid" integer, "AlertTime" timestamp with time zone)
LANGUAGE 'plpgsql'
COST 100.0
AS $function$
DECLARE
r record;
BEGIN
SELECT "DeviceID" , "AlertTime" FROM my_table
WHERE "AlertTime" BETWEEN starttime AND endtime;
END;
$function$;
ALTER FUNCTION public.my_function(timestamp with time zone, timestamp with time zone)
OWNER TO postgres;
When I am calling function with time '2016-12-15 00:00:01' to '2016-12-15 18:00:00' I am not getting any record. Even many records available for the time slot, I checked it by passing same time values for the query inside the function, its fetching data properly.
select * from my_function('2016-12-14 00:00:01','2016-12-15 18:00:00')
But when I am calling function with 2016-12-15 00:00:00' to '2016-12-15 18:00:00' I am able to get all records.
select * from my_function('2016-12-14 00:00:00','2016-12-15 18:00:00')
Even I tried to change the input parameters to "character varying" and convert the input internally to time stamp even then it is not working.
The explanation is obvious, isn't it?
All the matching rows from mytable have "AlertTime" greater or equal than 2016-12-14 00:00:00 and less than 2016-12-14 00:00:01.

Get data for every second in interval

I want to write a query which will results value between timestamp at every second and if value for particular time stamp is not there then it should result zero.for example
Start date time - 27/7/2015 10:00:00
End date time - 27/7/2015 10:05:00
Then result should be
27/7/2015 10:00:00 10 [start date time]
27/7/2015 10:00:01 19
27/7/2015 10:00:02 23
27/7/2015 10:00:03 0 [Value not present in table for this timestamp]
27/7/2015 10:00:04 45
27/7/2015 10:00:05 0 [Value not present in table for this timestamp]
...
27/7/2015 10:05:00 42 [end date time ]
I am trying this query but not getting desired result
SELECT CAST(date_trunc('second', CAST(to_timestamp(t1.timestamp_col,'DD-MM-YYYY HH24:MI:SS')as timestamp without time zone) + interval '1 second') as text)
, NULLIF(t1.y_temperature_col,'00')
FROM historical_trend_data t1
WHERE CAST(to_timestamp(t1.timestamp_col,'DD-MM-YYYY HH24:MI:SS') as timestamp without time zone) BETWEEN CAST(to_timestamp('28/7/2015 10:00:00','DD-MM-YYYY HH24:MI:SS')as timestamp without time zone) AND CAST(to_timestamp('28/7/2015 18:00:00','DD-MM-YYYY HH24:MI:SS') as timestamp without time zone);
This is the Function
CREATE OR REPLACE FUNCTION timestampwise_sp2(IN startdatetime text, IN enddatetime text,
OUT x_time_col text, OUT temperature text)
RETURNS SETOF record AS $BODY$
BEGIN
return query
with simul_data as(
SELECT generate_series(startdatetime::timestamp,
enddatetime::timestamp, '1 Seconds') As x_time_col
)
Select simul_data.x_time_col::text, coalesce(t1.y_temperature_col, '0') AS temperature
from historical_trend_data t1
LEFT JOIN simul_data ON CAST(to_timestamp(t1.timestamp_col,'DD-MM-YYYY HH24:MI:SS') as timestamp without time zone) = simul_data.x_time_col;
END; $BODY$ LANGUAGE plpgsql VOLATILE;
but it is not resulting the desired result
SELECT s.sec AS obs_time, coalesce(t1.y_temperature_col, 0) AS temperature
FROM generate_series('28-07-2015 10:00:00'::timestamp,
'28-07-2015 18:00:00'::timestamp, '1 minute') AS s(sec)
LEFT JOIN t1 ON timestamp_col = s.sec;
The generate_series() function returns a set of records in a sequence, in this case second intervals from a starting point to an ending point. That is left-joined to your actual data and any NULL values for the temperature are converted to 0 with the coalesce() function.
In function form it would be like this:
CREATE FUNCTION timestampwise_sp2(startdatetime timestamp, enddatetime timestamp) AS $$
SELECT to_char(simul_data.x_time_col, 'DD/MM/YYYY HH24:MI:SS') AS x_time_col,
coalesce(t1.y_temperature_col, '0') AS temperature
FROM generate_series($1, $2, '1 second') AS simul_data(x_time_col)
LEFT JOIN historical_trend_data t1
ON to_timestamp(t1.timestamp_col,'DD/MM/YYYY HH24:MI:SS') = simul_data.x_time_col;
$$ LANGUAGE sql STRICT;
Note that this is a sql language function, which is faster than plpgsql.