How to select rows by time interval in PostgreSQL? - sql

I have a table with timestamp field and usage field like this
timestamp | usage
'2015-06-13 13:45:58' | 240
'2015-06-13 13:45:59' | 480
'2015-06-13 13:46:00' | 240
'2015-06-13 13:46:01' | 320
...
I want to get sum of usage for a period of '1 week' with '30 minutes' interval.
I can get data only for intervals in minute, hour, day and ...
SELECT date_trunc('minute', timestamp) as clock, sum(usage)
FROM my_table
WHERE timestamp > localtimestamp - INTERVAL '1 week'
GROUP BY clock
how to get data for intervals like '5 minutes', '30 minutes', '2 days' and ... .

Use the following to get what your after.
SELECT TIMESTAMP WITH TIME ZONE 'epoch' + INTERVAL '1 second' * round((extract('epoch' FROM timestamp) / 1800) * 1800) AS clock, SUM(usage)
FROM my_table
WHERE timestamp > localtimestamp - INTERVAL '1 week'
GROUP BY round(extract('epoch' FROM timestamp) / 1800)

Related

Combine 2 series of timestamps in BigQuery

I'm trying to generate 2 series of timestamps with 30 minute interval like so:
interval_start,interval_end
2023-01-30 05:30:00.000000 +00:00,2023-01-30 06:00:00.000000 +00:00
2023-01-30 05:00:00.000000 +00:00,2023-01-30 05:30:00.000000 +00:00
2023-01-30 04:30:00.000000 +00:00,2023-01-30 05:00:00.000000 +00:00
I can generate each series but cannot combine them:
select *
from unnest(GENERATE_TIMESTAMP_ARRAY('2020-01-01', '2021-01-01', interval 30 minute)) start_times
select *
from unnest(GENERATE_TIMESTAMP_ARRAY(TIMESTAMP_ADD('2020-01-01', interval 30 MINUTE), '2021-01-01', interval 30 minute)) end_times
Consider below:
WITH intervals AS (
select *
from unnest(GENERATE_TIMESTAMP_ARRAY('2020-01-01', '2021-01-01', interval 30 minute)) interval_start
)
SELECT
interval_start, TIMESTAMP_ADD(interval_start, interval 30 minute) interval_end
FROM intervals
Output:

Taking this column of dates and grouping them 1-30, 31- 59, 60 - 89, +90, both upcoming and those dates that have passes

enter image description hereI have a dates column that I am trying to group in to specific groups. Some dates have passed other dates are in the future. I want to have group them by 1-30, 31- 59, 60 - 89, +90, so there would be 8 groups total for both those that have passed and those that are on the horizon. Below is what I have wrote so far, but I feel like I am over complicating and the more tweaks I make the more incorrect it becomes. Any insight is appreciated!
CASE WHEN DATEDIFF(CURRENT_DATE(),`date`) >= 90 THEN '90 days past'
WHEN DATEDIFF(CURRENT_DATE(),`date`) >= 60 THEN '60 days past'
WHEN DATEDIFF(CURRENT_DATE(),`date`) >= 30 THEN '30 days past'
WHEN DATEDIFF(`date`,CURRENT_DATE()) >= 90 THEN '90 days future'
WHEN DATEDIFF(`date`,CURRENT_DATE()) >= 60 THEN '60 days future'
WHEN DATEDIFF(`date`,CURRENT_DATE()) >= 30 THEN 'Next 30 days'
ELSE 'Not Late'
END
I think it's okay.
I would just use the same datediff for each.
create table test (`date` date);
insert into test values
( date_add(current_date, interval -90 day) ),
( date_add(current_date, interval -89 day) ),
( date_add(current_date, interval -60 day) ),
( date_add(current_date, interval -59 day) ),
( date_add(current_date, interval -30 day) ),
( date_add(current_date, interval -29 day) ),
( date_add(current_date, interval 29 day) ),
( date_add(current_date, interval 30 day) ),
( date_add(current_date, interval 59 day) ),
( date_add(current_date, interval 60 day) ),
( date_add(current_date, interval 89 day) ),
( date_add(current_date, interval 90 day) )
select `date`,
CASE
WHEN DATEDIFF(`date`, CURRENT_DATE) <= -90 THEN '90 days past'
WHEN DATEDIFF(`date`, CURRENT_DATE) <= -60 THEN '60 days past'
WHEN DATEDIFF(`date`, CURRENT_DATE) <= -30 THEN '30 days past'
WHEN DATEDIFF(`date`, CURRENT_DATE) >= 90 THEN '90 days future'
WHEN DATEDIFF(`date`, CURRENT_DATE) >= 60 THEN '60 days future'
WHEN DATEDIFF(`date`, CURRENT_DATE) >= 30 THEN 'Next 30 days'
ELSE 'Not Late'
END as status
from test
date | status
:--------- | :-------------
2021-10-12 | 90 days past
2021-10-13 | 60 days past
2021-11-11 | 60 days past
2021-11-12 | 30 days past
2021-12-11 | 30 days past
2021-12-12 | Not Late
2022-02-08 | Not Late
2022-02-09 | Next 30 days
2022-03-10 | Next 30 days
2022-03-11 | 60 days future
2022-04-09 | 60 days future
2022-04-10 | 90 days future
db<>fiddle here

Numbering dates from Sunday to Saturday

Does anyone have a easy solution to make a numbering from sunday to saturday and generate the dates in PostgreSQL(version 11).I have the below solution but it is limited to only 5 weeks and i need something that is flexible.
I have dates as a column in my source table, i want those dates to be numbered from saturday to sunday like below.
Current Query
WITH CTE AS
(
SELECT 1 as rno,generate_series( date_trunc('week', current_date)::date - 1
, date_trunc('week', current_date)::date + 5
, interval '1 day') current_week
)
,CTE_1 AS
(
SELECT rno,current_week FROM CTE
UNION
select 2,dt::date d from generate_series( (SELECT MIN(current_week)::DATE FROM CTE)- interval '7 days', (SELECT MIN(current_week)::DATE FROM CTE)- interval '1 days', interval '1 days') dt
)
,CTE_2 AS
(
SELECT rno,current_week FROM CTE_1
UNION
select 3,dt::date d from generate_series( (SELECT MIN(current_week)::DATE FROM CTE_1)- interval '7 days', (SELECT MIN(current_week)::DATE FROM CTE_1)- interval '1 days', interval '1 days') dt
)
,CTE_3 AS
(
SELECT rno,current_week FROM CTE_2
UNION
select 4,dt::date d from generate_series( (SELECT MIN(current_week)::DATE FROM CTE_2)- interval '7 days', (SELECT MIN(current_week)::DATE FROM CTE_2)- interval '1 days', interval '1 days') dt
)
,last_5_weeks as
(
SELECT rno,current_week FROM CTE_3
UNION
select 5,dt::date d from generate_series( (SELECT MIN(current_week)::DATE FROM CTE_3)- interval '7 days', (SELECT MIN(current_week)::DATE FROM CTE_3)- interval '1 days', interval '1 days') dt
)
SELECT rno,current_week::DATE as selected_date FROM last_5_weeks order by selected_date DESC
Current output
rno Date
1 "2020-10-24"
1 "2020-10-23"
1 "2020-10-22"
1 "2020-10-21"
1 "2020-10-20"
1 "2020-10-19"
1 "2020-10-18"
2 "2020-10-17"
2 "2020-10-16"
2 "2020-10-15"
2 "2020-10-14"
2 "2020-10-13"
2 "2020-10-12"
2 "2020-10-11"
3 "2020-10-10"
3 "2020-10-09"
3 "2020-10-08"
3 "2020-10-07"
3 "2020-10-06"
3 "2020-10-05"
3 "2020-10-04"
4 "2020-10-03"
4 "2020-10-02"
4 "2020-10-01"
4 "2020-09-30"
4 "2020-09-29"
4 "2020-09-28"
4 "2020-09-27"
5 "2020-09-26"
5 "2020-09-25"
5 "2020-09-24"
5 "2020-09-23"
5 "2020-09-22"
5 "2020-09-21"
5 "2020-09-20"
How about using arithmetics?
select 1 + (row_number() over(order by dt desc) - 1) / 7 rn, dt::date dt
from generate_series(
date_trunc('week', current_date)::date + 5 - interval '5 week -1 day',
date_trunc('week', current_date)::date + 5,
'1 day'
) s(dt)
order by dt desc
generate_series() produces all dates at once. You control the number of weeks that are generated with the value given to week in the literal interval. Then, in the outer query, we use row_number() to enumerate the week numbers.
Demo on DB Fiddle
One sequence for weeks and another one for days. It is flexible, 5 is a parameter. date_trunc('week',now()+'P1W'::interval)::date-2 is this week's saturday.
select
w rno,
date (date_trunc('week',now()+'P1W'::interval)::date-2 + make_interval(weeks => 1-w, days => 1-d)) "Date"
from generate_series(1, 5, 1) w
cross join generate_series(1, 7, 1) d;

PostgreSQL time range duration over time series with default end if null

I need to be able to calculate the duration (in seconds) between two time stamps as an aggregate over a time series using a default end_datetime if it is null.
Imagine you have something like a punch card when you puch in and out:
username, start_datetime, end_datetime
What I want is a generated time series of the last N minutes with the duration for all users that overlap within that time frame. So it would be the SUM(end_datetime - start_datetime) where you would COALESCE a default end_datetime if it is null.
So the basic pieces I think I need are:
Generate the time interval:
select TIMESTAMP '2013-01-01 12:01:00' - (interval '1' minute * generate_series(0,5)) as timestamps;
COALESCE a default end_datetime
COALESCE(end_datetime, NOW())
Figure out the seconds difference between the start and end dates
So if one user logged in at 11:56:50 and it is now 12:01:40 we should get a table like:
timestamps duration
-------------------------------------
2013-01-01 12:01:00 40
2013-01-01 12:00:00 60
2013-01-01 11:59:00 60
2013-01-01 11:58:00 60
2013-01-01 11:57:00 60
2013-01-01 11:56:00 10
with t as (select '2013-01-01 11:56:50'::timestamp startt, '2013-01-01 12:01:40'::timestamp endt)
select
timestamps,
extract(epoch from
case
when timestamps=date_trunc('minute',startt) then date_trunc('minute',startt) + interval '1 minute' - startt
when timestamps =date_trunc('minute',endt) then endt- date_trunc('minute',endt)
else interval '60 seconds' end) as durations
from
(select generate_series(date_trunc('minute',startt),date_trunc('minute',endt),'1 minute') timestamps, * from t) a
order by
timestamps desc;
2013-01-01 12:01:00;40
2013-01-01 12:00:00;60
2013-01-01 11:59:00;60
2013-01-01 11:58:00;60
2013-01-01 11:57:00;60
2013-01-01 11:56:00;10
If you have multiple rows with start and end timestamp than the following will work:
select
id,
timestamps,
extract(epoch from
case
when timestamps=date_trunc('minute',startt) then date_trunc('minute',startt) + interval '1 minute' - startt
when timestamps =date_trunc('minute',endt) then endt- date_trunc('minute',endt)
else interval '60 seconds' end) as durations
from
(
select
id,
generate_series(date_trunc('minute',startt) ,
coalesce(date_trunc('minute',endt),date_trunc('minute',Now())),'1 minute') as timestamps,
startt, endt
from test
) a
order by
id, timestamps desc
SQLFiddle

How to query a column with an awkward date format

Here is how the date is formatted:
XXXXXX001221
This date is for Dec 21
If I want to get yesterdays entries using the CURDATE() function how would I do this?
If you're just doing month-day (indices are based on the string format of CURDATE(), which is YYYY-MM-DD):
SELECT * FROM table_name
WHERE date_field LIKE
CONCAT('%', SUBSTR(DATE_SUB(CURDATE(), INTERVAL 1 DAY) FROM 6 FOR 2),
SUBSTR(DATE_SUB(CURDATE(), INTERVAL 1 DAY) FROM 9 FOR 2));
Or an alternative way using EXTRACT():
SELECT * FROM table_name
WHERE date_field LIKE
CONCAT('%', EXTRACT(MONTH FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)),
EXTRACT(DAY FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)));
For year as well:
SELECT * FROM table_name
WHERE date_field LIKE
CONCAT('%', SUBSTR(DATE_SUB(CURDATE(), INTERVAL 1 DAY) FROM 3 FOR 2),
SUBSTR(DATE_SUB(CURDATE(), INTERVAL 1 DAY) FROM 6 FOR 2),
SUBSTR(DATE_SUB(CURDATE(), INTERVAL 1 DAY) FROM 9 FOR 2));
SELECT * FROM table_name
WHERE date_field LIKE
CONCAT('%', EXTRACT(YEAR FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)),
EXTRACT(MONTH FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)),
EXTRACT(DAY FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)));
EDIT: Instead of WHERE date_field LIKE ... you can use WHERE RIGHT(date_field, 6) = ... as mentioned in codethis' answer:
SELECT * FROM table_name
WHERE RIGHT(date_field, 4) =
CONCAT(EXTRACT(MONTH FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)),
EXTRACT(DAY FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)));
SELECT * FROM table_name
WHERE RIGHT(date_field, 6) =
CONCAT(EXTRACT(YEAR FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)),
EXTRACT(MONTH FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)),
EXTRACT(DAY FROM DATE_SUB(CURDATE(), INTERVAL 1 DAY)));
NOTE: You can also use a variable to save calculating DATE_SUB(CURDATE(), INTERVAL 1 DAY) all the time.
SET #yesterday = DATE_SUB(CURDATE(), INTERVAL 1 DAY);
SELECT ...
Try something like:
SELECT * FROM table WHERE RIGHT(date_col, 6) = (SELECT CONVERT(VARCHAR(6), GETDATE()-1, 12) AS [YYMMDD])