Oracle SQL List Intervals - sql

I need to create new interval rows based on a start datetime column and an end datetime column.
My statement looks like this currently
select id,
startdatetime,
enddatetime
from calls
result looks like this
id startdatetime enddatetime
1 01/01/2020 00:00:00 01/01/2020 04:00:00
I would like a result like this
id startdatetime enddatetime Intervals
1 01/01/2020 00:00:00 01/01/2020 03:00:00 01/01/2020 00:00:00
1 01/01/2020 00:00:00 01/01/2020 03:00:00 01/01/2020 01:00:00
1 01/01/2020 00:00:00 01/01/2020 03:00:00 01/01/2020 02:00:00
1 01/01/2020 00:00:00 01/01/2020 03:00:00 01/01/2020 03:00:00
Thanking you in advance
p.s. I'm new to SQL

You can use a recursive sub-query factoring clause to loop and incrementally add an hour:
WITH times ( id, startdatetime, enddatetime, intervals ) AS (
SELECT id,
startdatetime,
enddatetime,
startdatetime
FROM calls c
UNION ALL
SELECT id,
startdatetime,
enddatetime,
intervals + INTERVAL '1' HOUR
FROM times
WHERE intervals + INTERVAL '1' HOUR <= enddatetime
)
SELECT *
FROM times;
outputs:
ID | STARTDATETIME | ENDDATETIME | INTERVALS
-: | :------------------ | :------------------ | :------------------
1 | 2020-01-01 00:00:00 | 2020-01-01 04:00:00 | 2020-01-01 00:00:00
1 | 2020-01-01 00:00:00 | 2020-01-01 04:00:00 | 2020-01-01 01:00:00
1 | 2020-01-01 00:00:00 | 2020-01-01 04:00:00 | 2020-01-01 02:00:00
1 | 2020-01-01 00:00:00 | 2020-01-01 04:00:00 | 2020-01-01 03:00:00
1 | 2020-01-01 00:00:00 | 2020-01-01 04:00:00 | 2020-01-01 04:00:00
db<>fiddle here

You can use the hierarchy query as following:
SQL> WITH CALLS (ID, STARTDATETIME, ENDDATETIME)
2 AS ( SELECT 1,
3 TO_DATE('01/01/2020 00:00:00', 'dd/mm/rrrr hh24:mi:ss'),
4 TO_DATE('01/01/2020 04:00:00', 'dd/mm/rrrr hh24:mi:ss')
5 FROM DUAL)
6 -- Your query starts from here
7 SELECT
8 ID,
9 STARTDATETIME,
10 ENDDATETIME,
11 STARTDATETIME + ( COLUMN_VALUE / 24 ) AS INTERVALS
12 FROM
13 CALLS C
14 CROSS JOIN TABLE ( CAST(MULTISET(
15 SELECT LEVEL - 1
16 FROM DUAL
17 CONNECT BY LEVEL <= TRUNC(24 *(ENDDATETIME - STARTDATETIME))
18 ) AS SYS.ODCINUMBERLIST) )
19 ORDER BY INTERVALS;
ID STARTDATETIME ENDDATETIME INTERVALS
---------- ------------------- ------------------- -------------------
1 01/01/2020 00:00:00 01/01/2020 04:00:00 01/01/2020 00:00:00
1 01/01/2020 00:00:00 01/01/2020 04:00:00 01/01/2020 01:00:00
1 01/01/2020 00:00:00 01/01/2020 04:00:00 01/01/2020 02:00:00
1 01/01/2020 00:00:00 01/01/2020 04:00:00 01/01/2020 03:00:00
SQL>
Cheers!!

Related

Oracle SQL: to count the records based on fixed time frame (say 15 or 30 minutes)

I have a table similar to
Start time | End Time | User |
09/02/2021 03:01:13 | 09/02/2021 03:45:15 | ABC |
09/02/2021 03:15:20 | 09/02/2021 05:03:20 | XYZ |
09/02/2021 06:03:12 | 09/02/2021 06:15:30 | DEF |
Expecting output:
StDt | EndDt | Count(1)
09/02/2021 00:00:00 | 09/02/2021 01:00:00 | 0
09/02/2021 01:00:00 | 09/02/2021 02:00:00 | 0
09/02/2021 02:00:00 | 09/02/2021 03:00:00 | 0
09/02/2021 03:00:00 | 09/02/2021 04:00:00 | 2
09/02/2021 04:00:00 | 09/02/2021 05:00:00 | 1
09/02/2021 05:00:00 | 09/02/2021 06:00:00 | 0
09/02/2021 06:00:00 | 09/02/2021 07:00:00 | 1
The interval in this example is hourly but i would like to keep it flexible for 10 mins/15 mins/30 mins.
I want this to be written in single sql.
All i could work out till now is how to generate the range.
select t1.StartDt, t1.EndDt from
(
select
(to_char(timestamp '2021-02-09 00:00:00' + numtodsinterval(rownum*60,'MINUTE') - numtodsinterval(60,'MINUTE'),'DD-MM-YYYY hh24:mi')) as StartDt,
(to_char(timestamp '2021-02-09 00:00:00' + numtodsinterval(rownum*60,'MINUTE'),'DD-MM-YYYY hh24:mi')) as EndDt
from dual connect by level <= 24
) t1;
I dont know how to link to the table mentioned above to get the data in the format i require.
You have such a nice startup, except keep the timestamp format for the time values within the subquery, and move TO_CHAR formatting to the main query at the result displaying phase along with using correlated subquery with distinctly count aggregation for the overlapping intervals, and use bind variables as the placeholder for the time portion values(60,30,15) such as
SQL> var min number
SQL> exec :min := 60
PL/SQL procedure successfully completed
min
---------
60
SQL> SELECT TO_CHAR(t.StartDt,'DD-MM-YYYY HH24:MI') AS StartDt,
2 TO_CHAR(t.EndDt,'DD-MM-YYYY HH24:MI') AS EndDt,
3 ( SELECT COUNT(DISTINCT "User")
FROM tab
WHERE t.EndDt >= Start_Time
AND t.StartDt <= End_Time ) AS Count
4 FROM
5 (
6 SELECT timestamp '2021-02-09 00:00:00' +
7 numtodsinterval(rownum * :min, 'MINUTE') -
8 numtodsinterval(:min, 'MINUTE') AS StartDt,
9 timestamp '2021-02-09 00:00:00' +
10 numtodsinterval(rownum * :min, 'MINUTE') AS EndDt
11 FROM dual
12 CONNECT BY level <= 24
13 ) t
14 ORDER BY StartDt;
STARTDT ENDDT COUNT
---------------- ---------------- ----------
09-02-2021 00:00 09-02-2021 01:00 0
09-02-2021 01:00 09-02-2021 02:00 0
09-02-2021 02:00 09-02-2021 03:00 0
09-02-2021 03:00 09-02-2021 04:00 2
09-02-2021 04:00 09-02-2021 05:00 1
09-02-2021 05:00 09-02-2021 06:00 1
09-02-2021 06:00 09-02-2021 07:00 1
09-02-2021 07:00 09-02-2021 08:00 0
.....
.....
Demo

SQL How fill in last values of time series if date is missing

In example: I have got the following table.
WITH
-- your input ....
input(t,grp,value) AS (
SELECT TIMESTAMP '2020-05-28 00:00:00','A',55
UNION ALL SELECT TIMESTAMP '2020-05-28 00:00:00','B',1.09
UNION ALL SELECT TIMESTAMP '2020-05-28 00:00:00','C',1.8
UNION ALL SELECT TIMESTAMP '2020-05-29 00:00:00','A',68
UNION ALL SELECT TIMESTAMP '2020-05-29 00:00:00','B',1.9
UNION ALL SELECT TIMESTAMP '2020-05-29 00:00:00','C',1.19
UNION ALL SELECT TIMESTAMP '2020-06-01 00:00:00','A',10
UNION ALL SELECT TIMESTAMP '2020-06-01 00:00:00','B',15
UNION ALL SELECT TIMESTAMP '2020-06-01 00:00:00','C',0.88
UNION ALL SELECT TIMESTAMP '2020-06-02 00:00:00','A',22
UNION ALL SELECT TIMESTAMP '2020-06-02 00:00:00','B',15
UNION ALL SELECT TIMESTAMP '2020-06-02 00:00:00','C',13
UNION ALL SELECT TIMESTAMP '2020-06-03 00:00:00','A',66
UNION ALL SELECT TIMESTAMP '2020-06-03 00:00:00','B',88
UNION ALL SELECT TIMESTAMP '2020-06-03 00:00:00','C',99
)
As you can see between dates 2020-30-05 and 2020-31-05 are missing in this table. So it is necessary to fill these dates with 2020-29-05 information grouped by GROUP. Additionally today date is larger than in the data (06-03 vs 06-08) (so in current month these observations are missing. As a result the final output should be like that :
date2 Group number
2020-28-05 00:00:00 A 55
2020-28-05 00:00:00 B 1.09
2020-28-05 00:00:00 C 1.8
2020-29-05 00:00:00 A 68
2020-29-05 00:00:00 B 1.9
2020-29-05 00:00:00 C 1.19
2020-30-05 00:00:00 A 68
2020-30-05 00:00:00 B 1.9
2020-30-05 00:00:00 C 1.19
2020-31-05 00:00:00 A 68
2020-31-05 00:00:00 B 1.9
2020-31-05 00:00:00 C 1.19
2020-01-06 00:00:00 A 10
2020-01-06 00:00:00 B 15
2020-01-06 00:00:00 C 0.88
2020-02-06 00:00:00 A 22
2020-02-06 00:00:00 B 15
2020-02-06 00:00:00 C 13
2020-03-06 00:00:00 A 66
2020-03-06 00:00:00 B 88
2020-03-06 00:00:00 C 99
And for periods 03-06 till 08-06 the same values
2020-08-06 00:00:00 A 66
2020-08-06 00:00:00 B 88
2020-08-06 00:00:00 C 99
The following code helps to find missing value in the dates, however those gaps are not filled up today dates. How to fix it?
SELECT ts AS t, grp, TS_FIRST_VALUE(value,'const') AS value
FROM input
TIMESERIES ts AS '1 DAY' OVER(PARTITION BY grp ORDER BY t)
ORDER BY 1,2
It's called INTERPOLATE and not EXTRAPOLATE, and that's the challenge.
You'll need to add the last row per group, but with today's date instead of the actual/original date, to the input table.
Note the padding and padded common table expressions I'm using below. Vertica has the analytic limit clause that I'm using here: LIMIT 1 OVER(PARTITION BY grp ORDER BY tmstmp DESC)..
WITH
input(tmstmp,grp,nbr) AS (
SELECT TIMESTAMP '2020-05-28 00:00:00','A',55
UNION ALL SELECT TIMESTAMP '2020-05-28 00:00:00','B',1.09
UNION ALL SELECT TIMESTAMP '2020-05-28 00:00:00','C',1.8
UNION ALL SELECT TIMESTAMP '2020-05-29 00:00:00','A',68
UNION ALL SELECT TIMESTAMP '2020-05-29 00:00:00','B',1.9
UNION ALL SELECT TIMESTAMP '2020-05-29 00:00:00','C',1.19
UNION ALL SELECT TIMESTAMP '2020-06-01 00:00:00','A',10
UNION ALL SELECT TIMESTAMP '2020-06-01 00:00:00','B',15
UNION ALL SELECT TIMESTAMP '2020-06-01 00:00:00','C',0.88
UNION ALL SELECT TIMESTAMP '2020-06-02 00:00:00','A',22
UNION ALL SELECT TIMESTAMP '2020-06-02 00:00:00','B',15
UNION ALL SELECT TIMESTAMP '2020-06-02 00:00:00','C',13
UNION ALL SELECT TIMESTAMP '2020-06-03 00:00:00','A',66
UNION ALL SELECT TIMESTAMP '2020-06-03 00:00:00','B',88
UNION ALL SELECT TIMESTAMP '2020-06-03 00:00:00','C',99
)
,
padding AS (
SELECT
CURRENT_DATE::timestamp
, grp
, nbr
FROM input
LIMIT 1 OVER(PARTITION BY grp ORDER BY tmstmp DESC)
)
,
padded AS (
SELECT * FROM input
UNION ALL
SELECT * FROM padding
)
SELECT
ts AS tmstmp
, grp
, TS_FIRST_VALUE(nbr,'const') AS nbr
FROM padded
TIMESERIES ts AS '1 DAY' OVER(PARTITION BY grp ORDER BY tmstmp)
ORDER BY 1,2
;
-- out tmstmp | grp | nbr
-- out ---------------------+-----+-------
-- out 2020-05-28 00:00:00 | A | 55.00
-- out 2020-05-28 00:00:00 | B | 1.09
-- out 2020-05-28 00:00:00 | C | 1.80
-- out 2020-05-29 00:00:00 | A | 68.00
-- out 2020-05-29 00:00:00 | B | 1.90
-- out 2020-05-29 00:00:00 | C | 1.19
-- out 2020-05-30 00:00:00 | A | 68.00
-- out 2020-05-30 00:00:00 | B | 1.90
-- out 2020-05-30 00:00:00 | C | 1.19
-- out 2020-05-31 00:00:00 | A | 68.00
-- out 2020-05-31 00:00:00 | B | 1.90
-- out 2020-05-31 00:00:00 | C | 1.19
-- out 2020-06-01 00:00:00 | A | 10.00
-- out 2020-06-01 00:00:00 | B | 15.00
-- out 2020-06-01 00:00:00 | C | 0.88
-- out 2020-06-02 00:00:00 | A | 22.00
-- out 2020-06-02 00:00:00 | B | 15.00
-- out 2020-06-02 00:00:00 | C | 13.00
-- out 2020-06-03 00:00:00 | A | 66.00
-- out 2020-06-03 00:00:00 | B | 88.00
-- out 2020-06-03 00:00:00 | C | 99.00
-- out 2020-06-04 00:00:00 | A | 66.00
-- out 2020-06-04 00:00:00 | B | 88.00
-- out 2020-06-04 00:00:00 | C | 99.00
-- out 2020-06-05 00:00:00 | A | 66.00
-- out 2020-06-05 00:00:00 | B | 88.00
-- out 2020-06-05 00:00:00 | C | 99.00
-- out 2020-06-06 00:00:00 | A | 66.00
-- out 2020-06-06 00:00:00 | B | 88.00
-- out 2020-06-06 00:00:00 | C | 99.00
-- out 2020-06-07 00:00:00 | A | 66.00
-- out 2020-06-07 00:00:00 | B | 88.00
-- out 2020-06-07 00:00:00 | C | 99.00
-- out 2020-06-08 00:00:00 | A | 66.00
-- out 2020-06-08 00:00:00 | B | 88.00
-- out 2020-06-08 00:00:00 | C | 99.00
-- out 2020-06-09 00:00:00 | A | 66.00
-- out 2020-06-09 00:00:00 | B | 88.00
-- out 2020-06-09 00:00:00 | C | 99.00

How to generate series for date range with minutes interval in oracle?

In Postgres below query is working using generate_series function
SELECT dates
FROM generate_series(CAST('2019-03-01' as TIMESTAMP), CAST('2019-04-01' as TIMESTAMP), interval '30 mins') AS dates
Below query is also working in Oracle but only for date interval
select to_date('2019-03-01','YYYY-MM-DD') + rownum -1 as dates
from all_objects
where rownum <= to_date('2019-03-06','YYYY-MM-DD')-to_date('2019-03-01','YYYY-MM-DD')+1
SELECT dates
FROM generate_series(CAST('2019-03-01' as TIMESTAMP), CAST('2019-04-01' as TIMESTAMP), interval '30 mins') AS dates
I want same result in Oracle for below query
SELECT dates
FROM generate_series(CAST('2019-03-01' as TIMESTAMP), CAST('2019-04-01' as TIMESTAMP), interval '30 mins') AS dates
Use a hierarchical query:
SELECT DATE '2019-03-01' + ( LEVEL - 1 ) * INTERVAL '30' MINUTE AS dates
FROM DUAL
CONNECT BY DATE '2019-03-01' + ( LEVEL - 1 ) * INTERVAL '30' MINUTE <= DATE '2019-04-01';
Output:
| DATES |
| :------------------ |
| 2019-03-01 00:00:00 |
| 2019-03-01 00:30:00 |
| 2019-03-01 01:00:00 |
| 2019-03-01 01:30:00 |
| 2019-03-01 02:00:00 |
| 2019-03-01 02:30:00 |
| 2019-03-01 03:00:00 |
| 2019-03-01 03:30:00 |
| 2019-03-01 04:00:00 |
| 2019-03-01 04:30:00 |
| 2019-03-01 05:00:00 |
| 2019-03-01 05:30:00 |
...
| 2019-03-31 19:30:00 |
| 2019-03-31 20:00:00 |
| 2019-03-31 20:30:00 |
| 2019-03-31 21:00:00 |
| 2019-03-31 21:30:00 |
| 2019-03-31 22:00:00 |
| 2019-03-31 22:30:00 |
| 2019-03-31 23:00:00 |
| 2019-03-31 23:30:00 |
| 2019-04-01 00:00:00 |
db<>fiddle here

Join period of time, grouping by id

I have rows with periods of time that intersect for the same user. For example:
-------------------------------------------------------------
| ID_USER | START_DATE | END_DATE |
-------------------------------------------------------------
| 1 | 01/01/2018 08:00:00 | 01/01/2018 08:50:00 |
| 1 | 01/01/2018 08:15:00 | 01/01/2018 08:20:00 |
| 1 | 01/01/2018 08:45:00 | 01/01/2018 09:55:00 |
| 1 | 01/01/2018 15:45:00 | 01/01/2018 17:00:00 |
| 2 | 01/01/2018 08:45:00 | 01/01/2018 09:50:00 |
| 2 | 01/01/2018 09:15:00 | 01/01/2018 10:00:00 |
-------------------------------------------------------------
I want to avoid it. I would like to join rows in one single column, taking the starting date as the oldest and the ending date as the newest. The result of the above example would be:
-------------------------------------------------------------
| ID_USER | START_DATE | END_DATE |
-------------------------------------------------------------
| 1 | 01/01/2018 08:00:00 | 01/01/2018 09:55:00 |
| 1 | 01/01/2018 15:45:00 | 01/01/2018 17:00:00 |
| 2 | 01/01/2018 08:45:00 | 01/01/2018 10:00:00 |
-------------------------------------------------------------
Have you any idea how can I get the solution I want with a SQL sentence in Oracle?
You have two types of intersection; the first where one period exists entirely within another (e.g. your second row, 08:15-08:20), and the second where one period overlaps the start or end of another.
If you eliminate the first type then you can use lead and lag to peek ahead and behind at what's left; I've added a third data set for further fun:
select id_user, start_date, end_date,
case when start_date <= lag(end_date) over (partition by id_user order by start_date)
then null
else start_date
end as calc_start_date,
case when end_date >= lead(start_date) over (partition by id_user order by end_date)
then null
else end_date
end as calc_end_date
from your_table t1
where not exists (
select *
from your_table t2
where t2.id_user = t1.id_user
and t2.start_date <= t1.start_date and t2.end_date >= t1.end_date
and t2.rowid != t1.rowid
);
ID_USER START_DATE END_DATE CALC_START_DATE CALC_END_DATE
---------- ------------------- ------------------- ------------------- ----------------------
1 2018-01-01 08:00:00 2018-01-01 08:50:00 2018-01-01 08:00:00
1 2018-01-01 08:45:00 2018-01-01 09:55:00 2018-01-01 09:55:00
1 2018-01-01 15:45:00 2018-01-01 17:00:00 2018-01-01 15:45:00 2018-01-01 17:00:00
2 2018-01-01 08:45:00 2018-01-01 09:50:00 2018-01-01 08:45:00
2 2018-01-01 09:15:00 2018-01-01 10:00:00 2018-01-01 10:00:00
3 2018-01-01 08:00:00 2018-01-01 08:30:00 2018-01-01 08:00:00
3 2018-01-01 08:15:00 2018-01-01 08:45:00
3 2018-01-01 08:45:00 2018-01-01 09:15:00
3 2018-01-01 09:00:00 2018-01-01 09:30:00 2018-01-01 09:30:00
The not exists clause removed the first type.
You can then collapse what is left, firstly by eliminating the rows that overlapped both ends (in my extra rows for ID 3), which have both the lead and lag values as null; and then using lead and lag again to replace the remaining nulls with their adjacent rows' values:
select distinct id_user,
case when calc_start_date is null
then lag(calc_start_date) over (partition by id_user order by start_date)
else calc_start_date
end as start_date,
case when calc_end_date is null
then lead(calc_end_date) over (partition by id_user order by end_date)
else calc_end_date
end as end_date
from (
select id_user, start_date, end_date,
case when start_date <= lag(end_date) over (partition by id_user order by start_date)
then null
else start_date
end as calc_start_date,
case when end_date >= lead(start_date) over (partition by id_user order by end_date)
then null
else end_date
end as calc_end_date
from your_table t1
where not exists (
select *
from your_table t2
where t2.id_user = t1.id_user
and t2.start_date <= t1.start_date and t2.end_date >= t1.end_date
and t2.rowid != t1.rowid
)
)
where calc_start_date is not null
or calc_end_date is not null
order by id_user, start_date, end_date;
ID_USER START_DATE END_DATE
---------- ------------------- -------------------
1 2018-01-01 08:00:00 2018-01-01 09:55:00
1 2018-01-01 15:45:00 2018-01-01 17:00:00
2 2018-01-01 08:45:00 2018-01-01 10:00:00
3 2018-01-01 08:00:00 2018-01-01 09:30:00
It wouldn't entirely surprise me if there are edge cases I haven't considered and which cause problems, but hopefully will be a starting point anyway.
There are four steps required to get the result, represented with three subqueries and one main query:
1) increase END_DATE to the maximum thus far
This is required, as your END_DATE is not ordered, e.g. the first record intersects with the third record, but the second record doen't intersect with the third one.
ID_USER START_DATE END_DATE
---------- ------------------- -------------------
1 01.01.2018 08:00:00 01.01.2018 08:50:00
1 01.01.2018 08:15:00 01.01.2018 08:50:00
1 01.01.2018 08:45:00 01.01.2018 09:55:00
1 01.01.2018 15:45:00 01.01.2018 17:00:00
2 01.01.2018 08:45:00 01.01.2018 09:50:00
2 01.01.2018 09:15:00 01.01.2018 10:00:00
2) Define a new group for each non-overlapping chunk
Technically for the first record (per USER_ID) and for each record that doesn't overlap with ist predecessor - assign a new group_id (GRP)
ID_USER START_DATE END_DATE GRP
---------- ------------------- ------------------- ----------
1 01.01.2018 08:00:00 01.01.2018 08:50:00 1
1 01.01.2018 08:15:00 01.01.2018 08:50:00
1 01.01.2018 08:45:00 01.01.2018 09:55:00
1 01.01.2018 15:45:00 01.01.2018 17:00:00 4
2 01.01.2018 08:45:00 01.01.2018 09:50:00 1
2 01.01.2018 09:15:00 01.01.2018 10:00:00
3) Fill the Groups
Fill the NULLs with the last group Id assigned to enable GROUP BY.
ID_USER START_DATE END_DATE GRP2
---------- ------------------- ------------------- ----------
1 01.01.2018 08:00:00 01.01.2018 08:50:00 1
1 01.01.2018 08:15:00 01.01.2018 08:50:00 1
1 01.01.2018 08:45:00 01.01.2018 09:55:00 1
1 01.01.2018 15:45:00 01.01.2018 17:00:00 4
2 01.01.2018 08:45:00 01.01.2018 09:50:00 1
2 01.01.2018 09:15:00 01.01.2018 10:00:00 1
4) GROUP BY
The rest is simple, the dates are MIN and MAX within the group. You group on the kay (ID_USER) and teh GRP.
ID_USER START_DATE END_DATE
---------- ------------------- -------------------
1 01.01.2018 08:00:00 01.01.2018 09:55:00
1 01.01.2018 15:45:00 01.01.2018 17:00:00
2 01.01.2018 08:45:00 01.01.2018 10:00:00
The query
with myt1 as (
select ID_USER, START_DATE,
max(END_DATE) over (partition by ID_USER order by START_DATE) END_DATE
from my_table),
myt2 as (
select ID_USER,START_DATE, END_DATE,
case when (nvl(lag(END_DATE) over (partition by ID_USER order by START_DATE),START_DATE-1) < START_DATE ) then
row_number() over (partition by ID_USER order by START_DATE) end grp
from myt1
),
myt3 as (
select ID_USER,START_DATE, END_DATE,
last_value(grp ignore nulls) over (partition by ID_USER order by START_DATE) as grp2
from myt2
),
select
ID_USER,
min(START_DATE) START_DATE,
max(END_DATE) END_DATE
from myt3
group by ID_USER, GRP2
order by 1,2;
The data
create table my_table as
select 1 ID_USER, to_date('01/01/2018 08:00:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 08:50:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select 1 ID_USER, to_date('01/01/2018 08:15:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 08:20:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select 1 ID_USER, to_date('01/01/2018 08:45:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 09:55:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select 1 ID_USER, to_date('01/01/2018 15:45:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 17:00:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select 2 ID_USER, to_date('01/01/2018 08:45:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 09:50:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select 2 ID_USER, to_date('01/01/2018 09:15:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 10:00:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual;
You are looking for the MIN/MAX function:
SELECT MIN(aggregate_expression),MAX(aggregate_expression)
FROM tables
[WHERE conditions]
GROUP BY ID;
Reference:
https://www.techonthenet.com/oracle/functions/min.php

Splitting interval overlapping more days in PostgreSQL

I have a PostgreSQL table containing start timestamp and duration time.
timestamp | interval
------------------------------
2018-01-01 15:00:00 | 06:00:00
2018-01-02 23:00:00 | 04:00:00
2018-01-04 09:00:00 | 2 days 16 hours
What I would like is to have the interval splitted into every day like this:
timestamp | interval
------------------------------
2018-01-01 15:00:00 | 06:00:00
2018-01-02 23:00:00 | 01:00:00
2018-01-03 00:00:00 | 03:00:00
2018-01-04 09:00:00 | 15:00:00
2018-01-05 00:00:00 | 24:00:00
2018-01-06 00:00:00 | 24:00:00
2018-01-07 00:00:00 | 01:00:00
I am playing with generate_series(), width_bucket(), range functions, but I still can't find plausible solution. Is there any existing or working solution?
not sure about all edge cases, but this seems working:
t=# with c as (select *,min(t) over (), max(t+i) over (), tsrange(date_trunc('day',t),t+i) tr from t)
, mid as (
select distinct t,i,g,tr
, case when g < t then t else g end tt
from c
right outer join (select generate_series(date_trunc('day',min),date_trunc('day',max),'1 day') g from c) e on g <# tr order by 3,1
)
select
tt
, i
, case when tt+'1 day' > upper(tr) and t < g then upper(tr)::time::interval when upper(tr) - lower(tr) < '1 day' then i else g+'1 day' - tt end
from mid
order by tt;
tt | i | case
---------------------+-----------------+----------
2018-01-01 15:00:00 | 06:00:00 | 06:00:00
2018-01-02 23:00:00 | 04:00:00 | 01:00:00
2018-01-03 00:00:00 | 04:00:00 | 03:00:00
2018-01-04 09:00:00 | 2 days 16:00:00 | 15:00:00
2018-01-05 00:00:00 | 2 days 16:00:00 | 1 day
2018-01-06 00:00:00 | 2 days 16:00:00 | 1 day
2018-01-07 00:00:00 | 2 days 16:00:00 | 01:00:00
(7 rows)
also please mind that timestamp without time zone can fail you when comparing timestamps...