How to convert data from one row to multiple rows base on Date - sql

I wish to convert data from one row to multiple rows base on start_time and end_time.
INPUT DATA:
ID
Start_Time
End_Time
Down_Mins
ABC123
11/22/2022 12:01
11/29/2022 14:33
10232.47
I need to write SQL for this requirement:
OUTPUT_DATA:
ID
Start_Time
End_Time
Down_Mins
ABC123
11/22/2022 12:01
11/23/2022 7:00
1138.55
ABC123
11/23/2022 7:00
11/24/2022 7:00
1440
ABC123
11/24/2022 7:00
11/25/2022 7:00
1440
ABC123
11/25/2022 7:00
11/26/2022 7:00
1440
ABC123
11/26/2022 7:00
11/27/2022 7:00
1440
ABC123
11/27/2022 7:00
11/28/2022 7:00
1440
ABC123
11/28/2022 7:00
11/29/2022 7:00
1440
ABC123
11/29/2022 7:00
11/29/2022 14:33
453.92
enter image description here

You can use a recursive query to split the data into rows for each 24-hour period starting at 7am:
WITH days (id, start_time, day_end, end_time, day_mins, down_mins) AS (
SELECT id,
start_time,
LEAST(TRUNC(start_time - INTERVAL '7' HOUR) + INTERVAL '31' HOUR, end_time),
end_time,
LEAST((LEAST(TRUNC(start_time - INTERVAL '7' HOUR) + INTERVAL '31' HOUR, end_time) - start_time) * 24 * 60, down_mins),
down_mins - LEAST((LEAST(TRUNC(start_time - INTERVAL '7' HOUR) + INTERVAL '31' HOUR, end_time) - start_time) * 24 * 60, down_mins)
FROM table_name
UNION ALL
SELECT id,
day_end,
LEAST(day_end + INTERVAL '24' HOUR, end_time),
end_time,
LEAST((LEAST(day_end + INTERVAL '24' HOUR, end_time) - day_end) * 24 * 60, down_mins),
down_mins - LEAST((LEAST(day_end + INTERVAL '24' HOUR, end_time) - day_end) * 24 * 60, down_mins)
FROM days
WHERE day_end < end_time
AND down_mins > 0
)
SEARCH DEPTH FIRST BY id, start_time SET order_id
SELECT id,
start_time,
day_end AS end_time,
day_mins AS down_mins
FROM days;
Which, for the sample data:
CREATE TABLE table_name (ID, Start_Time, End_Time, Down_Mins) AS
SELECT 'ABC123',
DATE '2022-11-23' + INTERVAL '7' HOUR - NUMTODSINTERVAL(1138.55, 'MINUTE'),
DATE '2022-11-23' + INTERVAL '7' HOUR + NUMTODSINTERVAL(10232.47 - 1138.55, 'MINUTE'),
10232.47
FROM DUAL;
Outputs:
ID
START_TIME
END_TIME
DOWN_MINS
ABC123
2022-11-22 12:01:27
2022-11-23 07:00:00
1138.55
ABC123
2022-11-23 07:00:00
2022-11-24 07:00:00
1440
ABC123
2022-11-24 07:00:00
2022-11-25 07:00:00
1440
ABC123
2022-11-25 07:00:00
2022-11-26 07:00:00
1440
ABC123
2022-11-26 07:00:00
2022-11-27 07:00:00
1440
ABC123
2022-11-27 07:00:00
2022-11-28 07:00:00
1440
ABC123
2022-11-28 07:00:00
2022-11-29 07:00:00
1440
ABC123
2022-11-29 07:00:00
2022-11-29 14:33:55
453.916666666666666666666666666666666667
fiddle

Related

Oracle SQL: Using intervals to specify ranges of hours

I have power meter data stored in table MeterData:
create table MeterData (
MeterID VARCHAR2(10), DownloadCycle VARCHAR2(6), DateHour Date,
KWH Number(22,6), KW Number(22,6), KVA Number(22,6), KVAR Number(22,6),
CONSTRAINT UniqueDownload UNIQUE(MeterID, DownloadCycle, DateHour))
The data looks like this:
MeterID
DownloadCycle
DateHour
KWH
KW
KVA
KVAR
2319927
202206
13/06/2022 00:00
0.138
0.552
0.552
0
2500350
202206
13/06/2022 00:15
0.612
2.448
2.916
1.584
2500351
202206
13/06/2022 01:30
0.8
3.2
3.2358
0.48
2500352
202206
13/06/2022 04:00
0.288
1.152
1.44
0.864
2500353
202206
13/06/2022 05:30
0.90808
3.63232
4.32456
0
2500396
202206
13/06/2022 12:00
68.09
272.36
277.101157
51.04
2500446
202206
13/06/2022 18:15
0
0
0
0
2500453
202206
13/06/2022 21:00
2.772
11.088
11.088
0
2500472
202206
13/06/2022 23:30
64.8
259.2
305.788256
162.24
2500490
202206
14/06/2022 00:30
2.4
9.6
9.6
0
2501352
202206
14/06/2022 01:45
11.64
46.56
46.56
0
5187222
202206
14/06/2022 06:30
1.452
5.808
7.392
0
5284288
202206
14/06/2022 11:00
66.792
267.168
267.447334
149.336
5516997
202206
14/06/2022 18:30
0.384
1.536
8.112
0
I need to assign every record in table MeterData to a range of hours stored in table HourlyBlocks, in which I'm using intervals as the starting and ending hours:
create table HourlyBlocks (
HourlyBlock VARCHAR2(6) UNIQUE, BlockStart INTERVAL DAY(1) TO SECOND(0),
BlockEnd INTERVAL DAY(1) TO SECOND(0));
insert into HourlyBlocks values (
'Rest', interval '0 05:00:00' day to second, interval '0 18:00:00' day to second);
insert into HourlyBlocks values (
'Peak', interval '0 18:00:00' day to second, interval '0 23:00:00' day to second);
insert into HourlyBlocks values (
'Valley', interval '0 23:00:00' day to second, interval '1 05:00:00' day to second);
(HourlyBlock 'Valley' begins at 23:00:00 and ends at 05:00:00 of the following day).
To test to which HourlyBlock every record in MeterData belongs, I extract the record's hour, minute and second information as an interval with the following, adding 1 day to the interval if it is less than 05:00:00 and thus belongs to HourlyBlock 'Valley':
select distinct m.MeterID, m.DateHour, m.kwh,
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') + case when
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') <= interval '00 05:00:00'
day to second then interval '1' day else interval '0' day end as intval,
h.HourlyBlock
from MeterData m, HourlyBlocks h
where (NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour ), 'DAY') > h.BlockStart
and NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour ), 'DAY') + case when
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour ), 'DAY') <= interval '00 05:00:00'
day to second then interval '1' day else interval '0' day end <= h.BlockEnd)
The HourlyBlock are correctly assigned, except for records where DateHour is between 00:00:00 and 05:00:00!
What am I doing wrong?
The expected output for the sample data provided would be:
|MeterID|DateHour |KWH |intval |HourlyBlock|
|-------|----------------|------|-------------------|-----------|
|2319927|13/06/2022 00:00|0.138 |+00 00:00:00.000000|Valley |
|2500350|13/06/2022 00:15|0.612 |+01 00:15:00.000000|Valley |
|2500351|13/06/2022 01:30|0.8 |+01 01:30:00.000000|Valley |
|2500352|13/06/2022 04:00|0.288 |+01 04:00:00.000000|Valley |
|2500353|13/06/2022 05:30|0.908 |+00 05:30:00.000000|Rest |
|2500396|13/06/2022 12:00|68.09 |+00 12:00:00.000000|Rest |
|2500446|13/06/2022 18:15|0 |+00 18:15:00.000000|Peak |
|2500453|13/06/2022 21:00|2.772 |+00 21:00:00.000000|Peak |
|2500472|13/06/2022 23:30|64.8 |+00 23:30:00.000000|Valley |
|2500490|14/06/2022 00:30|2.4 |+01 00:30:00.000000|Valley |
|2501352|14/06/2022 01:45|11.64 |+01 01:45:00.000000|Valley |
|5187222|14/06/2022 06:30|1.452 |+00 06:30:00.000000|Rest |
|5284288|14/06/2022 11:00|66.792|+00 11:00:00.000000|Rest |
|5516997|14/06/2022 18:30|0.384 |+00 18:30:00.000000|Peak |
(I'm sorry I had to format the output as code. It was the only way around that pesky "Your post appears to contain code that is not properly formatted as code" error.)
I found the fix, and simplified my WHERE clause:
select distinct m.MeterID, m.DateHour, m.kwh,
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') + case when
NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') <= interval '00 05:00:00'
day to second then interval '1' day else interval '0' day end as intval, h.HourlyBlock
from MeterData m, HourlyBlocks h
where NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY') +
case when NUMTODSINTERVAL(m.DateHour - trunc(m.DateHour), 'DAY')
<= interval '00 05:00:00' day to second then interval '1' day else
interval '0' day end between h.BlockStart + interval '1' second and h.BlockStart

Oracle sql create agenda

I have a table with interval dates and times. Can i create a full list with this data?
Table example:
Start_Date, End_Date, Start_Time, End_Time, Interval
01-jun-2021 02-jun-2021 08:00 10:00 30
03-jun-2021 04-jun-2021 10:00 12:00 15
Result:
01-jun-2021 08:00
01-jun-2021 08:30
01-jun-2021 09:00
01-jun-2021 09:30
02-jun-2021 08:00
02-jun-2021 08:30
02-jun-2021 09:00
02-jun-2021 09:30
03-jun-2021 10:00
03-jun-2021 10:15
03-jun-2021 10:30
03-jun-2021 11:00
03-jun-2021 11:15
03-jun-2021 11:30
03-jun-2021 11:45
04-jun-2021 10:00
04-jun-2021 10:15
04-jun-2021 10:30
04-jun-2021 11:00
04-jun-2021 11:15
04-jun-2021 11:30
04-jun-2021 11:45
Thanks.
This is a handy place to use a recursive CTE:
with cte (start_date, end_date, interval) as (
select to_date(start_date||start_time, 'DD-Mon-YYYYHH24:MI'), to_date(end_date||end_time, 'DD-Mon-YYYYHH24:MI'), interval
from t
union all
select cte.start_date + cte.interval * interval '1' minute, end_date, interval
from cte
where cte.start_date < end_date
)
select cast(start_date as timestamp)
from cte
order by start_date;
Here is a db<>fiddle.
You can use a recursive CTE, but the logic has to skip to the next day when you reach the end time; so this works:
with rcte (date_time, end_date, start_int, end_int, step_int) as (
select
start_date + to_dsinterval('0 ' || start_time || ':00'),
end_date,
to_dsinterval('0 ' || start_time || ':00'),
to_dsinterval('0 ' || end_time || ':00'),
interval * interval '1' minute
from your_table
union all
select
case
when date_time + step_int < trunc(date_time) + end_int
then date_time + step_int
else trunc(date_time) + interval '1' day + start_int
end,
end_date,
start_int,
end_int,
step_int
from rcte
where date_time + step_int < end_date + end_int
)
select date_time
from rcte
order by date_time
DATE_TIME
-------------------
2021-06-01 08:00:00
2021-06-01 08:30:00
2021-06-01 09:00:00
2021-06-01 09:30:00
2021-06-02 08:00:00
2021-06-02 08:30:00
2021-06-02 09:00:00
2021-06-02 09:30:00
2021-06-03 10:00:00
2021-06-03 10:15:00
2021-06-03 10:30:00
2021-06-03 10:45:00
2021-06-03 11:00:00
2021-06-03 11:15:00
2021-06-03 11:30:00
2021-06-03 11:45:00
2021-06-04 10:00:00
2021-06-04 10:15:00
2021-06-04 10:30:00
2021-06-04 10:45:00
2021-06-04 11:00:00
2021-06-04 11:15:00
2021-06-04 11:30:00
2021-06-04 11:45:00
db<>fiddle showing the anchor member including converting the times and interval to real day to second intervals types for later use; the anchor and recursive members with all the intermediate columns; and finally just this version with a single column.
You can format the resulting date value however you want, of course.

How to generate series of 24hrs with 1 hour interval and display the last as 23:59:59

Project: BIRT
Datasource: Amazon Redshift
I want to generate a Data Set with value of:
00:00:00
1:00:00
2:00:00
3:00:00
4:00:00
5:00:00
6:00:00
7:00:00
8:00:00
9:00:00
10:00:00
11:00:00
12:00:00
13:00:00
14:00:00
15:00:00
16:00:00
17:00:00
18:00:00
19:00:00
20:00:00
21:00:00
22:00:00
23:00:00
23:59:59 //the last value should display like this
I was able to generate a series of 24hours with 1 hr interval, but I need to make the last one's value as 23:59:59
Query to generate 24 hours with 1 hour interval:
SELECT start_date + gs * interval '1 hour' as times
FROM (
SELECT '2019-05-21 00:00:00'::timestamp as start_date, generate_series(1,24, 1) as gs)
How is that?
Thanks
Updating your query, just adding a if for the last hour:
SELECT
start_date + gs * interval '1 hour'
- if(gs=24, interval '1 second', interval '0 second') as times
FROM (
SELECT
'2019-05-21 00:00:00'::timestamp as start_date
, generate_series(1,24, 1) as gs
)
I think too much about this, the simplest way to achieve this is just add a default value on the report parameter , if you're going to use the data set in the report parameter
or with this:
SELECT start_date + gs * interval '1 hour' as times
FROM (
SELECT '2020-01-01 00:00:00'::timestamp as start_date, generate_series(1,24, 1) as gs)
union
select '2020-01-01 23:59:59'::timestamp as start_date

Return the end of the interval in Returning Functions?

In this case, 9.24. "Set Returning Functions" of the PostgreSQL 9.5 manual, only the initial dates and time are returned. Is it possible to return the date and time of the end of each interval?
SELECT * FROM generate_series('2008-03-01 00:00'::timestamp,
'2008-03-04 12:00', '10 hours');
generate_series
---------------------
2008-03-01 00:00:00
2008-03-01 10:00:00
2008-03-01 20:00:00
2008-03-02 06:00:00
2008-03-02 16:00:00
2008-03-03 02:00:00
2008-03-03 12:00:00
2008-03-03 22:00:00
2008-03-04 08:00:00
(9 rows)
Is this what you want?
SELECT gs.dte, LEAD(gs.dte) OVER (ORDER BY gs.dte) as next_dte
FROM generate_series('2008-03-01 00:00'::timestamp,
'2008-03-04 12:00',
'10 hours'
) gs(dte);
Or, if you don't want NULL for the last interval, explicitly do the calculation:
SELECT gs.dte, (gs.dte + interval '10 hours') as end_date
FROM generate_series('2008-03-01 00:00'::timestamp,
'2008-03-04 12:00',
'10 hours'
) gs(dte);

oracle sql Time to Resolve Calculation

I have a question and hopefully someone can help, because i have been stuck on this for a long time.
I have a column with remaining minutes for a task to expire and i want to calculate when this task will expire within the business days timeframe starting from the current sysdate day lets say weekdays from 09:00 to 17:00.
| Task No | Minutes Remaining | Expiration date |
| Task1 | 1800 | 27-10-16 9:45 AM |
| Task2 | 3400 | 28-10-16 9:45 AM |
| Task3 | 400 | 29-10-16 9:45 AM |
| Task4 | 180 | 30-10-16 9:45 AM |
| Task5 | 8400 | 31-10-16 9:45 AM |
| Task6 | 5000 | 1-11-16 9:45 AM |
OK, this was a fun problem. To summarize: You are given a date (which in Oracle always includes the time-of-day) from which you start measurement, and an initial duration in minutes. You need to find the expiration date (meaning date and time-of-day as always), which is calculated by adding the duration in minutes to the "clock-starting" date, but the clock should only run during business hours - 9 to 17, Monday to Friday only (not on weekends).
I assume if the "minutes remaining" is 0, then the expiration should be the same as the "clock-starting" date if it falls within work hours, or 9 am on the next work day otherwise.
To understand the solution, let's break it down in two parts. First let's consider a very special case: the "clock starts" on a Monday at 9 am. Then break down minutes remaining into an integer multiple of 2400 (5*8*60 = 2400 minutes in a full work week), plus an integer multiple of 480 from what's left (480 minutes to a work day), plus whatever is left, if anything. Then: the expiration date is the "clock-starting" date, plus however many weeks, plus however many whole days (between 0 and 4), plus the remaining minutes. One exceptional case here: if the "minutes remaining" is an exact multiple of 480 minutes, then expiration is at 5 pm on a certain work day, and not 9 am on the next work day. This requires special handling in the formula. All this is done in the outer query (at the bottom of the solution below).
Then we need to reduce the general case to this special case. This is done in the subquery prep in the solution. I simply increase the "minutes remaining" by the work minutes elapsed from 9 am on Monday at the beginning of the week. This is a relatively simple computation. Note that if the "clock starting" date is after 5 pm on a Friday (or any time on Saturday or Sunday), I must add exactly 2400 minutes, a full work week.
In the solution, I show a variety of "clock starting" dates, dt, and minutes remaining, rm. I tested a variety of situations, and I think the solution is correct, but you may want to test on more data (other situations I didn't include in the tests).
with
inputs ( task, min_rem, dt ) as (
select 'Task1', 1800, to_date('27-10-16 9:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task2', 3400, to_date('28-10-16 9:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task3', 400, to_date('29-10-16 3:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task4', 180, to_date('30-10-16 9:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task5', 8400, to_date('31-10-16 9:45 PM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task6', 5000, to_date('01-11-16 5:00 PM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task7', 0, to_date('01-12-16 5:00 PM', 'dd-mm-yy hh:mi PM') from dual
),
prep ( task, min_rem, dt, adj_min, adj_dt ) as (
select task, min_rem, dt,
min_rem + case when dt > trunc(dt, 'iw') + 5 + 17/24 then 2400
else (trunc(dt) - trunc(dt, 'iw')) * 480 +
least(480, greatest(0, 1440 * (dt - trunc(dt) - 9/24)))
end,
trunc(dt, 'iw') + 9/24
from inputs
)
select task, min_rem, dt,
adj_dt + 7 * trunc(adj_min / 2400)
+ case when adj_min/480 = trunc(adj_min/480)
then mod(adj_min, 2400) / 480 - 1 + 8/24
else trunc(mod(adj_min, 2400) / 480) + mod(adj_min, 480) / 1440
end as expiration
from prep
order by task
;
Output:
TASK MIN_REM DT EXPIRATION
----- ---------- ----------------- -----------------
Task1 1800 27-10-16 09:45 AM 01-11-16 03:45 PM
Task2 3400 28-10-16 09:45 AM 08-11-16 10:25 AM
Task3 400 29-10-16 03:45 AM 31-10-16 03:40 PM
Task4 180 30-10-16 09:45 AM 31-10-16 12:00 PM
Task5 8400 31-10-16 09:45 PM 24-11-16 01:00 PM
Task6 5000 01-11-16 05:00 PM 16-11-16 12:20 PM
Task7 0 01-12-16 05:00 PM 01-12-16 05:00 PM
7 rows selected