Timestamp difference in numeric - postgresql

I want to get the difference between two timestamps in days with decimals.
i.e. instead of interval, 3 days 20:33:54.937,
the desired output is 3.85 (in numeric format)
+---------------------+---------------------+--+
| date1 | date_2 | |
+---------------------+---------------------+--+
| 2020-12-22 08:10:11 | 2020-12-18 11:36:16 | |
+---------------------+---------------------+--+

Easiest thing is to get a number of seconds (with epoch) and then divide that into days
SELECT extract(epoch from (timestamptz '2021-01-12 13:55:23.631389+00' - timestamp '2021-01-09 09:55:23.631389+00')) / (24*60*60);
?column?
--------------------
3.1666666666666665
(1 row)

demo:db<>fiddle
You can use EXTRACT(epoch FROM your_interval) which returns the seconds of the interval. These can be converted into days:
SELECT EXTRACT(
epoch FROM (
'2020-12-22 08:10:11'::timestamp - '2020-12-18 11:36:16'::timestamp
)
)/3600/24

Related

Postgres generate_series joined onto result set to fill empty dates within a range

I have a result set that sometimes has missing dates (because no data is present within that week), and need to fill those with zero's. For simplicity I've reduced the query and table down to
Table: generated_data
id | data | date_key
1 | 3 | 2021-12-13 03:00:00.000
2 | 1 | 2021-12-22 05:00:00.000
3 | 4 | 2021-12-24 07:00:00.000
4 | 7 | 2022-01-03 01:00:00.000
5 | 2 | 2022-01-05 02:00:00.000
Query:
Select
sum(data) / count(data),
DATE_TRUNC('week', date_key AT TIME ZONE 'America/New_York') as date_key
from generated_data
group by DATE_TRUNC('week', date_key AT TIME ZONE 'America/New_York') as date_key
would produce the following result set:
3 | 2021-12-13 00:00:00.000
2.5 | 2021-12-20 00:00:00.000
5.5 | 2022-01-03 00:00:00.000
but as you can see there's a missing date of 12/27 which I'd like to return in the result set as a zero. I've looked into using generate_series and joining onto the above simplified query, but haven't found a good solution.
The idea would be doing something like
SELECT GENERATE_SERIES('2021-11-08T00:00:00+00:00'::date, '2022-01-17T04:59:59.999000+00:00'::date, '1 week'::interval) as date_key
but I'm not sure how to join that back to the result query where just the missing dates are added. What would a on clause look like for something like that?
final result set would look like
3 | 2021-12-13 00:00:00.000
2.5 | 2021-12-20 00:00:00.000
0 | 2021-12-27 00:00:00.000
5.5 | 2022-01-03 00:00:00.000
At first, you should find the min and max of date and generate based on that. Then join a table with generated data
Demo
WITH data_range AS (
SELECT
min(date_key) AT TIME ZONE 'America/New_York' min,
max(date_key) AT TIME ZONE 'America/New_York' max
from generated_data
),
generated_range AS (
SELECT DATE_TRUNC(
'week',
GENERATE_SERIES(min, max, '1 week'::interval)
) AS date FROM data_range
)
SELECT
coalesce(sum(data) / count(data), 0),
DATE_TRUNC('week', gr.date)
FROM
generated_range gr
LEFT JOIN generated_data gd ON
DATE_TRUNC('week', gd.date_key AT TIME ZONE 'America/New_York') = gr.date
GROUP BY DATE_TRUNC('week', gr.date)
ORDER BY 2

Week Day Starting from a Certain Day (01 Jan 2021) in Postgres

I am trying to get week numbers in a Year starting from a certain day
I've checked the stack but quite confused.
SELECT EXTRACT(WEEK FROM TIMESTAMP '2021-01-01'),
extract('year' from TIMESTAMP '2021-01-01')
The output is 53|2021
I want it to be 01|2021
I understand the principle of the isoweek but I want the year to start in 01-01-2021
The aim is to use intervals from this day to determine week numbers
Week N0| End Date
1 | 01-01-2021
2 | 01-08-2021
5 | 01-29-2021
...
This is really strange way to determine the week number, but in the end it's a simple math operation: the number of days since January first divided by 7.
You can create a function for this:
create function custom_week(p_input date)
returns int
as
$$
select (p_input - date_trunc('year', p_input)::date) / 7 + 1;
$$
language sql
immutable;
So this:
select date, custom_week(date)
from (
values
(date '2021-01-01'),
(date '2021-01-08'),
(date '2021-01-29')
) as v(date)
yields
date | custom_week
-----------+------------
2021-01-01 | 1
2021-01-08 | 2
2021-01-29 | 5

Postgres time comparaison with time zone

Today I encounter a strange postgres behavious. Let me explain:
Here is my table I will work on.
=># \d planning_time_slot
Table "public.planning_time_slot"
Column | Type | Collation | Nullable | Default
-------------+---------------------------+-----------+----------+------------------------------------------------
id | integer | | not null | nextval('planning_time_slot_id_seq'::regclass)
planning_id | integer | | not null |
day | character varying(255) | | not null |
start_time | time(0) without time zone | | not null |
end_time | time(0) without time zone | | not null |
day_id | integer | | not null | 0
Indexes:
"planning_time_slot_pkey" PRIMARY KEY, btree (id)
"idx_a9e3f3493d865311" btree (planning_id)
Foreign-key constraints:
"fk_a9e3f3493d865311" FOREIGN KEY (planning_id) REFERENCES planning(id)
what i want to do is something like:
select * from planning_time_slot where start_time > (CURRENT_TIME AT TIME ZONE 'Europe/Paris');
But it seems like postgres is comparing time before the time zone conversion.
Here is my tests:
=># select * from planning_time_slot where start_time > (CURRENT_TIME AT TIME ZONE 'Europe/Paris');
id | planning_id | day | start_time | end_time | day_id
-----+-------------+-----+------------+----------+--------
157 | 6 | su | 16:00:00 | 16:30:00 | 0
(1 row)
=># select (CURRENT_TIME AT TIME ZONE 'Europe/Paris');
timezone
--------------------
16:35:48.591002+02
(1 row)
When I try with a lot of entries it appears that the comparaison is done between start_time and CURRENT_TIME without the time zone cast.
For your information I also tried :
select * from planning_time_slot where start_time > timezone('Europe/Paris', CURRENT_TIME);
It has the exact same result.
I also tried to change the column type to time(0) with time zone. It makes the exact same result.
One last important point. I really need to set timezone I want, because later on I will change it dynamically depending on other stuffs. So it will not be 'Europe/Paris' everytime.
Does anyone have a clue or a hint please ?
psql (PostgreSQL) 11.2 (Debian 11.2-1.pgdg90+1)
(CURRENT_TIME AT TIME ZONE 'Europe/Paris') is, for example, 17:52:17.872082+02. But internally it is 15:52:17.872082+00. Both time and timetz (time with time zone) are all stored as UTC, the only difference is timetz is stored with a time zone. Changing the time zone does not change what point in time it represents.
So when you compare it with a time...
# select '17:00:00'::time < '17:52:17+02'::timetz;
?column?
----------
f
That is really...
# select '17:00:00'::time < '15:52:17'::time;
?column?
----------
f
Casting a timetz to a time will lop off the time zone.
test=# select (CURRENT_TIME AT TIME ZONE 'Europe/Paris')::time;
timezone
-----------------
17:55:57.099863
(1 row)
test=# select '17:00:00' < (CURRENT_TIME AT TIME ZONE 'Europe/Paris')::time;
?column?
----------
t
Note that this sort of comparison only makes sense if you want to store the notion that a thing happens at 17:00 according to the clock on the wall. For example, if you had a mobile phone game where an event starts "at 17:00" meaning 17:00 where the user is. This is referred to as a "floating time zone".
Assuming day is "day of week", I suggest storing it as an integer. It's easier to compare and localize.
Instead of separate start and end times, consider a single timerange. Then you can use range operators.
I think you have deeper problems.
You have a day, a start time and an end time, but no notion of time zone. So this will mean something different depending on the time zone of the observer.
I think you should add a tz column that stores which time zone that information is in. Then you can get the start time like this:
WHERE (day + start_time) AT TIME ZONE tz > current_timestamp

How to cast an int of microseconds into a interval field in postgres?

There is this question about how to extract microseconds from an interval field
I want to do the opposite, I want to create an interval from a numeric microseconds. How would I do this?
The reason is I want to take a table of this format
column_name | data_type
-------------+--------------------------
id | bigint
date | date
duration | numeric
and import it into a table like this
column_name | data_type
-------------+--------------------------
id | integer
date | date
duration | interval
Currently I am trying:
select CAST(duration AS interval) from boboon.entries_entry;
which gives me:
ERROR: cannot cast type numeric to interval
LINE 1: select CAST(duration AS interval) from boboon.entries_entry;
You can do:
select duration * interval '1 microsecond'
This is how you convert any date part to an interval in Postgres. Postgres supports microseconds, as well as more common units.
you can append the units and then cast to interval
example:
select (123.1234 || ' seconds')::interval
outputs:
00:02:12.1234
valid units are the following (and their plural forms):
microsecond
millisecond
second
minute
hour
day
week
month
quarter
year
decade
century
millennium

PostgreSQL format lag column

This query works great for finding the difference between successive rows:
select id, created_at, created_at - lag(created_at, 1)
over (order by created_at) as diff
from fistbumps where bumper_id = 2543
and created_at between '2012-01-11' and '2012-01-12' order by created_at;
...but the results come out like:
id | created_at | diff
--------+----------------------------+-----------------
197230 | 2012-01-11 00:04:31.774426 |
197231 | 2012-01-11 00:04:32.279181 | 00:00:00.504755
197232 | 2012-01-11 00:04:33.961665 | 00:00:01.682484
197233 | 2012-01-11 00:04:36.506685 | 00:00:02.54502
What would be really groovy is if I could format that diff column to just seconds and millis (e.g. 2.54502). I tried using date_trunc() and extract(), but I can't seem to get the syntax right.
The result of created_at - lag(create_at) is a value of type interval.
You can get the seconds of an interval using extract(epoch from interval_value)
So in your case it would be:
extract(epoch from (created_at - lag(created_at, 1)) )