SQL: working with timestamped energy meter data - sql

I have a table with energy usage data in fifteen minute intervals:
METER
TIMESTAMP
KWH
2500396
12/04/2022 23:15
131.01
2500396
12/04/2022 23:30
132.11
2500396
12/04/2022 23:45
125.84
2500396
13/04/2022 00:00
127.27
2500396
13/04/2022 00:15
123.86
2500396
13/04/2022 00:30
114.51
2500396
13/04/2022 00:45
117.7
2500396
13/04/2022 01:00
120.01
I need to calculate energy usage per hour, where, for example, usage during hour 23 of 12/04/2022 is the sum of the intervals from 12/04/2022 23:15 to 13/04/2022 00:00 (notice the change in date), and usage during hour 0 of 12/04/2022 is the sum of the intervals from 13/04/2022 00:15 to 13/04/2022 01:00.
I'll appreciate suggestions on how to code this concisely in Oracle SQL.

You can subtract 15 minutes from the times and then truncate to the start of the hour and then aggregate by that and the meter:
SELECT meter,
TRUNC(timestamp - INTERVAL '15' MINUTE, 'HH') AS hour,
SUM(kwh) AS total_kwh
FROM table_name
GROUP BY
meter,
TRUNC(timestamp - INTERVAL '15' MINUTE, 'HH');
Which, for the sample data:
CREATE TABLE table_name (METER, TIMESTAMP, KWH) AS
SELECT 2500396, DATE '2022-04-12' + INTERVAL '23:15' HOUR TO MINUTE, 131.01 FROM DUAL UNION ALL
SELECT 2500396, DATE '2022-04-12' + INTERVAL '23:30' HOUR TO MINUTE, 132.11 FROM DUAL UNION ALL
SELECT 2500396, DATE '2022-04-12' + INTERVAL '23:45' HOUR TO MINUTE, 125.84 FROM DUAL UNION ALL
SELECT 2500396, DATE '2022-04-13' + INTERVAL '00:00' HOUR TO MINUTE, 127.27 FROM DUAL UNION ALL
SELECT 2500396, DATE '2022-04-13' + INTERVAL '00:15' HOUR TO MINUTE, 123.86 FROM DUAL UNION ALL
SELECT 2500396, DATE '2022-04-13' + INTERVAL '00:30' HOUR TO MINUTE, 114.51 FROM DUAL UNION ALL
SELECT 2500396, DATE '2022-04-13' + INTERVAL '00:45' HOUR TO MINUTE, 117.70 FROM DUAL UNION ALL
SELECT 2500396, DATE '2022-04-13' + INTERVAL '01:00' HOUR TO MINUTE, 120.01 FROM DUAL;
Outputs:
METER
HOUR
TOTAL_KWH
2500396
2022-04-12 23:00:00
516.23
2500396
2022-04-13 00:00:00
476.08
db<>fiddle here

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:

Split row data base on timestamp SQL Oracle

Good day everyone. I have a table as below. Duration is the time from current state to next state.
Timestamp
State
Duration(minutes)
10/9/2022 8:50:00 AM
A
35
10/9/2022 9:25:00 AM
B
10
10/9/2022 9:35:00 AM
C
...
How do I split data at 9:00 AM of each day like below:
Timestamp
State
Duration(minutes)
10/9/2022 8:50:00 AM
A
10
10/9/2022 9:00:00 AM
A
25
10/9/2022 9:25:00 AM
B
10
10/9/2022 9:35:00 AM
C
...
Thank you.
Use a row-generator function to generate extra rows when the timestamp is before 09:00 and the next timestamp is after 09:00 (and calculate the diff value rather than storing it in the table):
SELECT l.ts AS timestamp,
t.state,
ROUND((l.next_ts - l.ts) * 24 * 60, 2) As diff
FROM (
SELECT timestamp,
LEAD(timestamp) OVER (ORDER BY timestamp) AS next_timestamp,
state
FROM table_name
) t
CROSS APPLY (
SELECT GREATEST(
t.timestamp,
TRUNC(t.timestamp - INTERVAL '9' HOUR) + INTERVAL '9' HOUR + LEVEL - 1
) AS ts,
LEAST(
t.next_timestamp,
TRUNC(t.timestamp - INTERVAL '9' HOUR) + INTERVAL '9' HOUR + LEVEL
) AS next_ts
FROM DUAL
CONNECT BY
TRUNC(t.timestamp - INTERVAL '9' HOUR) + INTERVAL '9' HOUR + LEVEL - 1 < t.next_timestamp
) l;
Which, for your sample data:
CREATE TABLE table_name (Timestamp, State) AS
SELECT DATE '2022-10-09' + INTERVAL '08:50' HOUR TO MINUTE, 'A' FROM DUAL UNION ALL
SELECT DATE '2022-10-09' + INTERVAL '09:25' HOUR TO MINUTE, 'B' FROM DUAL UNION ALL
SELECT DATE '2022-10-09' + INTERVAL '09:35' HOUR TO MINUTE, 'C' FROM DUAL UNION ALL
SELECT DATE '2022-10-12' + INTERVAL '09:35' HOUR TO MINUTE, 'D' FROM DUAL;
Outputs:
TIMESTAMP
STATE
DIFF
2022-10-09 08:50:00
A
10
2022-10-09 09:00:00
A
25
2022-10-09 09:25:00
B
10
2022-10-09 09:35:00
C
1405
2022-10-10 09:00:00
C
1440
2022-10-11 09:00:00
C
1440
2022-10-12 09:00:00
C
35
2022-10-12 09:35:00
D
null
fiddle

Calculating passed service hours

I'm trying to calculate the time that has passed since a service request has been logged (service time), based on service hours.
Start time is the time the ticket has been logged (date_logged), end time would be either the current time (for open tickets) or the date_closed for closed tickets.
Service hours vary depending on the department the ticket was assigned to, as listed in the example below (all the times and dates in my table have the data type datetime):
Ticket-Nr.
Department
date_logged
date_closed
start_mon
end_mon
start_tue
end_tue
start_wed
end_wed
start_trs
end_trs
start_fri
end_fri
start_sat
end_sat
1234567
A
06.01.21 11:30:52
01.01.2001 07:30
01.01.2001 16:45
01.01.2001 07:30
01.01.2001 16:45
01.01.2001 07:30
01.01.2001 16:45
01.01.2001 07:30
01.01.2001 16:45
01.01.2001 07:30
01.01.2001 13:00
8912345
B
13.01.21 09:14:16
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 15:00
6789012
C
14.01.21 10:48:01
14.01.21 11:40
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 16:30
01.01.2001 07:00
01.01.2001 15:00
3456789
D
15.01.2021 09:41:00
01.01.2001 08:00
01.01.2001 15:00
01.01.2001 08:00
01.01.2001 15:00
01.01.2001 08:00
01.01.2001 15:00
01.01.2001 08:00
01.01.2001 15:00
01.01.2001 08:00
01.01.2001 13:00
0123456
B
02.01.2021 13:12:00
...
...
...
...
...
The result should look something like this (minus the "current datetime" column, that's just for context):
Ticket-Nr.
department
date_logged
service time [hh:mm]
current datetime
date_closed
1234567
A
06.01.21 11:30:52
62:33
14.01.2021 12:03
8912345
B
13.01.21 09:14:16
12:19
14.01.2021 12:03
6789012
C
14.01.21 10:48:01
00:28
14.01.2021 12:03
14.01.21 11:40
...
...
...
...
...
...
Public holidays have to be included for department A, B and D.
You can directly calculate the hours (extending your previous question):
SELECT ticket_nr,
department,
date_logged,
current_datetime,
date_closed,
TO_CHAR( FLOOR( service_time_seconds / 60 / 60 ), 'FM99990' )
|| ':'
|| TO_CHAR( MOD( FLOOR( service_time_seconds / 60 ), 60 ), 'FM00' )
|| ':'
|| TO_CHAR( MOD( service_time_seconds, 60 ), 'FM00' )
AS "SERVICE_TIME HH:MM:SS"
FROM (
SELECT t.ticket_nr,
t.department,
t.date_logged,
SYSDATE AS current_datetime,
t.date_closed,
ROUND(
(
-- Calculate the full weeks difference from the start of ISO weeks.
(
TRUNC( COALESCE( date_closed, SYSDATE ), 'IW' )
- TRUNC( date_logged, 'IW' )
) * ( s.hours_mon
+ s.hours_tue
+ s.hours_wed
+ s.hours_thu
+ s.hours_fri
+ s.hours_sat
+ s.hours_sun ) / (7*24)
-- Add the hours for the full days for the final week.
+ DECODE(
TRUNC( COALESCE( date_closed, SYSDATE ) )
- TRUNC( COALESCE( date_closed, SYSDATE ), 'IW' ),
0, 0.0,
1, s.hours_mon,
2, s.hours_mon + s.hours_tue,
3, s.hours_mon + s.hours_tue + s.hours_wed,
4, s.hours_mon + s.hours_tue + s.hours_wed + s.hours_thu,
5, s.hours_mon + s.hours_tue + s.hours_wed + s.hours_thu + s.hours_fri,
6, s.hours_mon + s.hours_tue + s.hours_wed + s.hours_thu + s.hours_fri + s.hours_sat
) / 24
-- Subtract the hours for the full days from the days of the week
-- before the date logged.
- DECODE(
TRUNC( date_logged ) - TRUNC( date_logged, 'IW' ),
0, 0.0,
1, s.hours_mon,
2, s.hours_mon + s.hours_tue,
3, s.hours_mon + s.hours_tue + s.hours_wed,
4, s.hours_mon + s.hours_tue + s.hours_wed + s.hours_thu,
5, s.hours_mon + s.hours_tue + s.hours_wed + s.hours_thu + s.hours_fri,
6, s.hours_mon + s.hours_tue + s.hours_wed + s.hours_thu + s.hours_fri + s.hours_sat
) / 24
-- Add the hours of the final day
+ COALESCE(
GREATEST(
LEAST(
COALESCE( date_closed, SYSDATE ),
TRUNC( COALESCE( date_closed, SYSDATE ) )
+ DECODE(
TRUNC( COALESCE( date_closed, SYSDATE ) )
- TRUNC( COALESCE( date_closed, SYSDATE ), 'IW' ),
0, s.end_mon,
1, s.end_tue,
2, s.end_wed,
3, s.end_thu,
4, s.end_fri,
5, s.end_sat,
6, s.end_sun
)
)
-
(
TRUNC( COALESCE( date_closed, SYSDATE ) )
+ DECODE(
TRUNC( COALESCE( date_closed, SYSDATE ) )
- TRUNC( COALESCE( date_closed, SYSDATE ), 'IW' ),
0, s.start_mon,
1, s.start_tue,
2, s.start_wed,
3, s.start_thu,
4, s.start_fri,
5, s.start_sat,
6, s.start_sun
)
),
0
) / 24,
0
)
-- Subtract the hours of the day before the range starts.
+ COALESCE(
GREATEST(
LEAST(
date_logged,
date_logged
+ DECODE(
TRUNC( COALESCE( date_closed, SYSDATE ) )
- TRUNC( COALESCE( date_closed, SYSDATE ), 'IW' ),
0, s.end_mon,
1, s.end_tue,
2, s.end_wed,
3, s.end_thu,
4, s.end_fri,
5, s.end_sat,
6, s.end_sun
)
)
-
(
date_logged
+ DECODE(
TRUNC( COALESCE( date_closed, SYSDATE ) )
- TRUNC( COALESCE( date_closed, SYSDATE ), 'IW' ),
0, s.start_mon,
1, s.start_tue,
2, s.start_wed,
3, s.start_thu,
4, s.start_fri,
5, s.start_sat,
6, s.start_sun
)
),
0
) / 24,
0
)
)
-- Multiply to give seconds rather than fractions of full days.
* 24 * 60 * 60
) AS service_time_seconds
FROM table_name t
INNER JOIN service_hours s
ON ( s.department = t.department )
);
Which, for the sample data:
CREATE TABLE table_name ( Ticket_Nr, department, date_logged, date_closed ) AS
SELECT 1234567, 'A', DATE '2021-01-06' + INTERVAL '11:30:52' HOUR TO SECOND, NULL FROM DUAL UNION ALL
SELECT 8912345, 'B', DATE '2021-01-13' + INTERVAL '09:14:16' HOUR TO SECOND, NULL FROM DUAL UNION ALL
SELECT 6789012, 'C', DATE '2021-01-14' + INTERVAL '10:48:28' HOUR TO SECOND, DATE '2021-01-21' + INTERVAL '11:40:00' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 1, 'D', DATE '2021-01-07' + INTERVAL '07:00:00' HOUR TO SECOND, DATE '2021-01-14' + INTERVAL '07:00:00' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 2, 'A', DATE '2021-01-07' + INTERVAL '07:00:00' HOUR TO SECOND, DATE '2021-01-08' + INTERVAL '07:00:00' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 3, 'A', DATE '2021-01-08' + INTERVAL '07:00:00' HOUR TO SECOND, DATE '2021-01-09' + INTERVAL '07:00:00' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 4, 'A', DATE '2021-01-09' + INTERVAL '07:00:00' HOUR TO SECOND, DATE '2021-01-10' + INTERVAL '07:00:00' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 5, 'B', DATE '2021-01-09' + INTERVAL '07:00:00' HOUR TO SECOND, DATE '2021-01-10' + INTERVAL '07:00:00' HOUR TO SECOND FROM DUAL;
CREATE TABLE service_hours (
Department VARCHAR2(5)
CONSTRAINT service_hours__department__pk PRIMARY KEY,
start_mon INTERVAL DAY TO SECOND,
end_mon INTERVAL DAY TO SECOND,
hours_mon NUMBER
GENERATED ALWAYS AS (
COALESCE(
( DATE '1970-01-01' + end_mon ) - ( DATE '1970-01-01' + start_mon ),
0
) * 24
),
start_tue INTERVAL DAY TO SECOND,
end_tue INTERVAL DAY TO SECOND,
hours_tue NUMBER
GENERATED ALWAYS AS (
COALESCE(
( DATE '1970-01-01' + end_tue ) - ( DATE '1970-01-01' + start_tue ),
0
) * 24
),
start_wed INTERVAL DAY TO SECOND,
end_wed INTERVAL DAY TO SECOND,
hours_wed NUMBER
GENERATED ALWAYS AS (
COALESCE(
( DATE '1970-01-01' + end_wed ) - ( DATE '1970-01-01' + start_wed ),
0
) * 24
),
start_thu INTERVAL DAY TO SECOND,
end_thu INTERVAL DAY TO SECOND,
hours_thu NUMBER
GENERATED ALWAYS AS (
COALESCE(
( DATE '1970-01-01' + end_thu ) - ( DATE '1970-01-01' + start_thu ),
0
) * 24
),
start_fri INTERVAL DAY TO SECOND,
end_fri INTERVAL DAY TO SECOND,
hours_fri NUMBER
GENERATED ALWAYS AS (
COALESCE(
( DATE '1970-01-01' + end_fri ) - ( DATE '1970-01-01' + start_fri ),
0
) * 24
),
start_sat INTERVAL DAY TO SECOND,
end_sat INTERVAL DAY TO SECOND,
hours_sat NUMBER
GENERATED ALWAYS AS (
COALESCE(
( DATE '1970-01-01' + end_sat ) - ( DATE '1970-01-01' + start_sat ),
0
) * 24
),
start_sun INTERVAL DAY TO SECOND,
end_sun INTERVAL DAY TO SECOND,
hours_sun NUMBER
GENERATED ALWAYS AS (
COALESCE(
( DATE '1970-01-01' + end_sun ) - ( DATE '1970-01-01' + start_sun ),
0
) * 24
)
);
INSERT INTO service_hours (
Department,
start_mon, end_mon,
start_tue, end_tue,
start_wed, end_wed,
start_thu, end_thu,
start_fri, end_fri,
start_sat, end_sat,
start_sun, end_sun
)
SELECT 'A',
INTERVAL '07:30' HOUR TO MINUTE, INTERVAL '16:45' HOUR TO MINUTE,
INTERVAL '07:30' HOUR TO MINUTE, INTERVAL '16:45' HOUR TO MINUTE,
INTERVAL '07:30' HOUR TO MINUTE, INTERVAL '16:45' HOUR TO MINUTE,
INTERVAL '07:30' HOUR TO MINUTE, INTERVAL '16:45' HOUR TO MINUTE,
INTERVAL '07:30' HOUR TO MINUTE, INTERVAL '13:00' HOUR TO MINUTE,
NULL, NULL,
NULL, NULL
FROM DUAL UNION ALL
SELECT 'B',
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '15:00' HOUR TO MINUTE,
NULL, NULL
FROM DUAL UNION ALL
SELECT 'C',
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '16:30' HOUR TO MINUTE,
INTERVAL '07:00' HOUR TO MINUTE, INTERVAL '15:00' HOUR TO MINUTE,
NULL, NULL
FROM DUAL UNION ALL
SELECT 'D',
INTERVAL '08:00' HOUR TO MINUTE, INTERVAL '15:00' HOUR TO MINUTE,
INTERVAL '08:00' HOUR TO MINUTE, INTERVAL '15:00' HOUR TO MINUTE,
INTERVAL '08:00' HOUR TO MINUTE, INTERVAL '15:00' HOUR TO MINUTE,
INTERVAL '08:00' HOUR TO MINUTE, INTERVAL '15:00' HOUR TO MINUTE,
INTERVAL '08:00' HOUR TO MINUTE, INTERVAL '15:00' HOUR TO MINUTE,
NULL, NULL,
NULL, NULL
FROM DUAL;
Outputs:
TICKET_NR | DEPARTMENT | DATE_LOGGED | CURRENT_DATETIME | DATE_CLOSED | SERVICE_TIME HH:MM:SS
--------: | :--------- | :------------------------ | :------------------------ | :------------------------ | :--------------------
1234567 | A | 2021-01-06 11:30:52 (WED) | 2021-01-15 13:38:49 (FRI) | null | 61:13:45
8912345 | B | 2021-01-13 09:14:16 (WED) | 2021-01-15 13:38:49 (FRI) | null | 19:16:37
6789012 | C | 2021-01-14 10:48:28 (THU) | 2021-01-15 13:38:49 (FRI) | 2021-01-21 11:40:00 (THU) | 55:41:40
1 | D | 2021-01-07 07:00:00 (THU) | 2021-01-15 13:38:49 (FRI) | 2021-01-14 07:00:00 (THU) | 35:00:00
2 | A | 2021-01-07 07:00:00 (THU) | 2021-01-15 13:38:49 (FRI) | 2021-01-08 07:00:00 (FRI) | 9:15:00
3 | A | 2021-01-08 07:00:00 (FRI) | 2021-01-15 13:38:49 (FRI) | 2021-01-09 07:00:00 (SAT) | 5:30:00
4 | A | 2021-01-09 07:00:00 (SAT) | 2021-01-15 13:38:49 (FRI) | 2021-01-10 07:00:00 (SUN) | 0:00:00
5 | B | 2021-01-09 07:00:00 (SAT) | 2021-01-15 13:38:49 (FRI) | 2021-01-10 07:00:00 (SUN) | 8:00:00
db<>fiddle here

Splitting time into hour intervals in oracle (CTE)

So, my aim is to be able to count time spent on certain activities in hour ranges.
My data contains: start of the certain activity and end of that activity,
for example I know that someone had break from '2019-01-09 17:04:34' to '2019-01-09 19:55:03'.
My aim is to calculate that this person spent 55 minutes on break in interval '17-18', 60 minutes on '18-19' and 55 minutes on '19-20'.
My idea was to always split the source so for the row containing start and and of the activity I would receive as many rows as my time range split in the hour ranges (for this sample data I would receive 3: rows with '2019-01-09 17:04:34' to '2019-01-09 17:59:59', '2019-01-09 18:00:00' to '2019-01-09 18:59:59' and '2019-01-09 19:00:00' to '2019-01-09 19:55:03')
If I could obtain something like that I could manage to count all things I need to. I predict that to obtain this result I should use CTE (as we don't know in how many ranges we need to split time interval), but I have no experience in it.
Hopefully I managed to explain my problem clearly. I work on oracle sql developer.
I'd be very grateful for your help on at least some tips.
Since you mentioned recursion, this uses recursive subquery factoring:
-- CTE for sample data
with your_table (id, start_time, end_time) as (
select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03' from dual
union all
select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03' from dual
union all
select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07' from dual
union all
select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01' from dual
),
-- recursive CTE
rcte (id, hour_period, minutes, period_start_time, end_time, hour_num) as (
select id,
-- first period is the original start hour
extract(hour from start_time),
-- minutes in first period, which can end at the end of that hour, or at original
-- end time if earlier
case when extract(minute from end_time) = 0
and end_time >= cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour
then 60
else extract(minute from
least(cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour, end_time)
- start_time
)
end,
-- calculate next period start
cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour,
-- original end time
end_time,
-- first hour period (for later ordering)
1
from your_table
union all
select id,
-- this period's hour value
extract(hour from period_start_time),
-- minutes in this period - either 60 if we haven't reach the end time yet;
-- or if we have then the number of minutes from the end time
case when end_time < period_start_time + interval '1' hour
then extract(minute from end_time)
else 60
end,
-- calculate next period start
period_start_time + interval '1' hour,
-- original end time
end_time,
-- increment hour period (for later ordering)
hour_num + 1
from rcte
where period_start_time < end_time
)
select id, hour_period, minutes
from rcte
order by id, hour_num;
ID HOUR_PERIOD MINUTES
---------- ----------- ----------
1 17 55
1 18 60
1 19 55
2 23 12
2 0 60
2 1 60
2 2 5
3 18 1
4 13 60
4 14 0
It find finds the amount of time spent in the first hour of the period in the anchor member, then recursively looks at subsequent hours until the end time is reached, increasing the passed-on period end time each time; and in the recursive member it checks whether to use a fixed 60 minutes (if it knows the end time hasn't been reached) or use the actual minutes from the end time.
My example periods include ones that span midnight, cover less than an hour, and that start in the first minute of an hour - and which end in the first minute of an hour, which (in my calculation anyway) ends up with a row for that hour anyway and the number of minutes as zero. You can easily filter that out if you don't want to see it.
It is not entirely clear from your post how you want to handle non-zero seconds components (what combination of rounding and/or truncation). In any case, that can be coded easily, once a complete set of non-contradictory rules is agreed upon.
Other than that, your question consists of two parts: identify the proper hours for each id (each activity or event), and the duration of the part of that event during that hour. In the query below, using the CONNECT BY hierarchical technique, I generate the hours and the duration as an interval day to second. As I said, that can be converted to minutes (between 0 and 60) once you clarify the rounding rules.
with
your_table (id, start_time, end_time) as (
select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03'
from dual union all
select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03'
from dual union all
select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07'
from dual union all
select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01'
from dual
)
select id,
trunc(start_time, 'hh') + interval '1' hour * (level - 1) as hr,
case when level = 1 and connect_by_isleaf = 1
then end_time - start_time
when level = 1
then trunc(start_time, 'hh') + interval '1' hour - start_time
when connect_by_isleaf = 1
then end_time - trunc(end_time, 'hh')
else interval '1' hour
end as duration
from your_table
connect by trunc(start_time, 'hh') + interval '1' hour * (level - 1) < end_time
and prior id = id
and prior sys_guid() is not null
;
Output:
ID HR DURATION
---------- ------------------- -------------------
1 2019-01-09 17:00:00 +00 00:55:26.000000
1 2019-01-09 18:00:00 +00 01:00:00.000000
1 2019-01-09 19:00:00 +00 00:55:03.000000
2 2019-01-09 23:00:00 +00 00:12:59.000000
2 2019-01-10 00:00:00 +00 01:00:00.000000
2 2019-01-10 01:00:00 +00 01:00:00.000000
2 2019-01-10 02:00:00 +00 00:05:03.000000
3 2019-01-09 18:00:00 +00 00:01:06.000000
4 2019-01-09 13:00:00 +00 01:00:00.000000
4 2019-01-09 14:00:00 +00 00:00:01.000000

Get day number when all I have is day of the week

I was interested in MT0's answer on this question using intervals and dates. I was working through trying to find a different way to answer the question and I started to wonder about something.
Using just the intervals that MT0 set up:
with weekly_shifts(shift_date,start_time,end_time) as
(SELECT 'MON', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'TUE', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'WED', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'THU', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'FRI', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL)
If all I have is days of the week in DY format (MON,TUE,WED) and I want to get the number version of the day (2,3,4), what is the easiest way to do that?
My only idea that I could come up with was something like this:
select to_char(next_day(sysdate,shift_date),'D') SHIFT_NUM,
weekly_shifts.*
from weekly_shifts
You can create a look-up table with all the day names and numbers and then join to that:
CREATE TABLE day_numbers(day_number, day_name) AS
SELECT TO_CHAR(SYSDATE + LEVEL, 'D'),
TO_CHAR(SYSDATE + LEVEL, 'DY')
FROM DUAL
CONNECT BY LEVEL <= 7;
Or, you can generate it on the fly as part of the sub-query factoring clause:
WITH day_numbers(day_number, day_name) AS (
SELECT TO_CHAR(SYSDATE + LEVEL, 'D'),
TO_CHAR(SYSDATE + LEVEL, 'DY')
FROM DUAL
CONNECT BY LEVEL <= 7
),
weekly_shifts(shift_date,start_time,end_time) as (
SELECT 'MON', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'TUE', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'WED', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'THU', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'FRI', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL
)
db<>fiddle here
One possibility is using row_number() and rownum. Need to be thoroughly tested as your example probably simplified:
with weekly_shifts
AS
(
SELECT 'MON' shift_day, INTERVAL '09:00' HOUR TO MINUTE start_time, INTERVAL '18:00' HOUR TO MINUTE end_time FROM DUAL
UNION ALL
SELECT 'TUE', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL
UNION ALL
SELECT 'WED', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL
UNION ALL
SELECT 'THU', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL
UNION ALL
SELECT 'FRI', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL
UNION ALL
SELECT 'MON' shift_day, INTERVAL '09:00' HOUR TO MINUTE start_time, INTERVAL '18:00' HOUR TO MINUTE end_time FROM DUAL
UNION ALL
SELECT 'TUE', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL
UNION ALL
SELECT 'WED', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL
UNION ALL
SELECT 'THU', INTERVAL '10:00' HOUR TO MINUTE, INTERVAL '19:00' HOUR TO MINUTE FROM DUAL
UNION ALL
SELECT 'FRI', INTERVAL '09:00' HOUR TO MINUTE, INTERVAL '18:00' HOUR TO MINUTE FROM DUAL
)
SELECT ROW_NUMBER() OVER (PARTITION BY reset_week ORDER BY reset_week)+1 day_number, shift_day, start_time, end_time, reset_week
FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY shift_day ORDER BY rownum) reset_week, shift_day, start_time, end_time FROM weekly_shifts
ORDER BY rownum
)
ORDER BY reset_week
/
Output:
day_number shift_day start_time end_time reset_week
2 MON +00 09:00:00.000000 +00 18:00:00.000000 1
3 TUE +00 10:00:00.000000 +00 19:00:00.000000 1
4 WED +00 09:00:00.000000 +00 18:00:00.000000 1
5 THU +00 10:00:00.000000 +00 19:00:00.000000 1
6 FRI +00 09:00:00.000000 +00 18:00:00.000000 1
2 MON +00 09:00:00.000000 +00 18:00:00.000000 2
3 TUE +00 10:00:00.000000 +00 19:00:00.000000 2
4 WED +00 09:00:00.000000 +00 18:00:00.000000 2
5 THU +00 10:00:00.000000 +00 19:00:00.000000 2
6 FRI +00 09:00:00.000000 +00 18:00:00.000000 2