I'm trying to get time intervals based on a table.
My source table is something like this:
ID | OTHER_DATA | TIME_BEG | TIME_END | DURATION |
---|---|---|---|---|
1 | abcd | 10:00 | 11:00 | 15 |
2 | xyzt | 16:00 | 17:00 | 30 |
Desired output:
ID | OTHER_DATA | ITVL_BEG | ITVL_END |
---|---|---|---|
1 | abcd | 10:00 | 10:15 |
1 | abcd | 10:15 | 10:30 |
1 | abcd | 10:30 | 10:45 |
1 | abcd | 10:45 | 11:00 |
2 | xyzt | 16:00 | 16:30 |
2 | xyzt | 16:30 | 17:00 |
TIME_BEG
and TIME_END
are VARCHAR
columns but I also have them as DAY TO SECOND INTERVAL
which are not shown here (TIME_BEG_INT and TIME_END_INT respectively).
Basically I need to duplicate every row TRUNC (EXTRACT (DAY FROM 24 * 60 * (TIME_END_INT - TIME_BEG_INT)) / DURATION)
times and add this*DURATION to my dates, in one SQL.
Any help is appreciated.
CodePudding user response:
If you are using intervals:
CREATE TABLE table_name (ID, OTHER_DATA, TIME_BEG, TIME_END, DURATION) AS
SELECT 1, 'abcd', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '11:00' HOUR TO MINUTE, INTERVAL '15' MINUTE FROM DUAL UNION ALL
SELECT 2, 'xyzt', INTERVAL '16:00' HOUR TO MINUTE, INTERVAL '17:00' HOUR TO MINUTE, INTERVAL '30' MINUTE FROM DUAL;
Then you can use:
WITH range (ID, OTHER_DATA, TIME_BEG, TIME_INT_END, TIME_END, DURATION) AS (
SELECT ID,
OTHER_DATA,
TIME_BEG,
LEAST(time_beg duration, time_end),
TIME_END,
DURATION
FROM table_name
UNION ALL
SELECT ID,
OTHER_DATA,
TIME_INT_END,
LEAST(time_int_end duration, time_end),
TIME_END,
DURATION
FROM range
WHERE time_int_end < time_end
)
SEARCH DEPTH FIRST BY id SET id_order
SELECT ID,
OTHER_DATA,
TIME_BEG AS itvl_beg,
TIME_INT_END AS itvl_end
FROM range;
Which outputs:
ID OTHER_DATA ITVL_BEG ITVL_END 1 abcd 000000000 10:00:00.000000000 000000000 10:15:00.000000000 1 abcd 000000000 10:15:00.000000000 000000000 10:30:00.000000000 1 abcd 000000000 10:30:00.000000000 000000000 10:45:00.000000000 1 abcd 000000000 10:45:00.000000000 000000000 11:00:00.000000000 2 xyzt 000000000 16:00:00.000000000 000000000 16:30:00.000000000 2 xyzt 000000000 16:30:00.000000000 000000000 17:00:00.000000000
If you have the values as strings then you can convert them to intervals first:
CREATE TABLE table_name (ID, OTHER_DATA, TIME_BEG, TIME_END, DURATION) AS
SELECT 1, 'abcd', '10:00', '11:00', 15 FROM DUAL UNION ALL
SELECT 2, 'xyzt', '16:00', '17:00', 30 FROM DUAL;
WITH data(ID, OTHER_DATA, TIME_BEG, TIME_END, DURATION) AS (
SELECT ID,
OTHER_DATA,
TO_DSINTERVAL('0 '||TIME_BEG||':00'),
TO_DSINTERVAL('0 '||TIME_END||':00'),
NUMTODSINTERVAL(DURATION, 'MINUTE')
FROM table_name
),
range (ID, OTHER_DATA, TIME_BEG, TIME_INT_END, TIME_END, DURATION) AS (
SELECT ID,
OTHER_DATA,
TIME_BEG,
LEAST(time_beg duration, time_end),
TIME_END,
DURATION
FROM data
UNION ALL
SELECT ID,
OTHER_DATA,
TIME_INT_END,
LEAST(time_int_end duration, time_end),
TIME_END,
DURATION
FROM range
WHERE time_int_end < time_end
)
SEARCH DEPTH FIRST BY id SET id_order
SELECT ID,
OTHER_DATA,
TIME_BEG AS itvl_beg,
TIME_INT_END AS itvl_end
FROM range;
db<>fiddle here
CodePudding user response:
This is the classic solution based on the pre-interval DATE
calculation
with time_int(ID, OTHER_DATA, ITVL_BEG, ITVL_END, DURATION, TIME_END) as (
select
ID, OTHER_DATA, TIME_BEG,
to_char(to_date(time_beg,'HH24:mi') duration/(24*60),'HH24:mi'),
DURATION, TIME_END
from tab
union all
select
ID, OTHER_DATA, ITVL_END,
to_char(to_date(ITVL_END,'HH24:mi') duration/(24*60),'HH24:mi'),
DURATION, TIME_END
from time_int
where ITVL_END <= TIME_END
)
select
ID, OTHER_DATA, ITVL_BEG, ITVL_END
from time_int
order by 1,3;
ID OTHE ITVL_ ITVL_
---------- ---- ----- -----
1 abcd 10:00 10:15
1 abcd 10:15 10:30
1 abcd 10:30 10:45
1 abcd 10:45 11:00
1 abcd 11:00 11:15
2 xyzt 16:00 16:30
2 xyzt 16:30 17:00
2 xyzt 17:00 17:30
A recursive CTE is used with the step based on the following calculation to get the next interval boundary (assuming that your time is stored as VARCHAR2)
to_char(to_date(time_beg,'HH24:mi') duration/(24*60),'HH24:mi')
Note that you may convert 10:00
to date and Oracle provides the default missing day, month and year, that you do not need because after increasing by duration
you converts back to a HH24:MI
string.