Oracle sql create agenda - sql

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.

Related

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

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

Oracle splitting date range into day and custom time intervals

I am trying to split work shift date range into different date and time interval. Already found some answers, but still trying to split by time interval. Thanks in advance for any ideas or tips.
Each day need to be split out separately
Day Shift is 0600-22:00
Night Shift is 2200-0600
Range1:
2022-02-03 08:40 to 2022-02-04 10:07
Split Rows:
2022-02-03 08:40 to 2022-02-03 22:00 DAY
2022-02-03 22:00 to 2022-02-04 06:00 NIGHT
2022-02-04 06:00 to 2022-02-04 10:07 DAY
Range2:
2022-02-03 08:40 to 2022-02-04 02:07
Split Rows:
2022-02-03 08:40 to 2022-02-03 22:00 DAY
2022-02-03 22:00 to 2022-02-04 02:07 NIGHT
Range3:
2022-02-03 04:40 to 2022-02-04 02:07
Split Rows:
2022-02-03 04:40 to 2022-02-03 06:00 NIGHT
2022-02-03 08:40 to 2022-02-03 22:00 DAY
2022-02-03 22:00 to 2022-02-04 02:07 NIGHT
Sample data (Using lateral query is not working yet. I will update, if i figure it out) Also trying to see whether i can split them per hour and sum up later as in here splitting time into hour intervals
WITH SAMPLE AS (
SELECT
1 AS ID,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 10:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
UNION ALL
SELECT
2 AS ID,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
UNION ALL
SELECT
3 AS ID,
TO_DATE('2022-02-03 04:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
)
SELECT
ID,
L.STARTDATE,
L.ENDDATE
FROM
SAMPLE,
LATERAL (
SELECT
CASE LEVEL
WHEN 1 THEN STARTDATE
ELSE TRUNC(STARTDATE) + LEVEL - 1
END STARTDATE,
LEAST(TRUNC(STARTDATE) + LEVEL - 1 / 24 / 60, ENDDATE) ENDDATE
FROM
DUAL
CONNECT BY
TRUNC(STARTDATE) + LEVEL - 1 <= ENDDATE
) L;
You can use:
WITH SAMPLE (ID, startdate, enddate ) AS (
SELECT 1,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI'),
TO_DATE('2022-02-04 10:07', 'YYYY-MM-DD HH24:MI')
FROM DUAL
UNION ALL
SELECT 2,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI'),
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI')
FROM DUAL
UNION ALL
SELECT 3,
TO_DATE('2022-02-03 04:40', 'YYYY-MM-DD HH24:MI'),
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI')
FROM DUAL
)
SELECT ID,
o.type,
GREATEST(L.start_date + o.start_offset, s.startdate) AS startdate,
LEAST(L.start_date + o.end_offset, s.enddate) AS enddate
FROM SAMPLE s
CROSS JOIN LATERAL (
SELECT TRUNC(startdate - INTERVAL '6' HOUR)
+ INTERVAL '6' HOUR
+ LEVEL - 1 AS start_date
FROM DUAL
CONNECT BY
TRUNC(startdate - INTERVAL '6' HOUR)
+ INTERVAL '6' HOUR
+ LEVEL - 1
< ENDDATE
) L
CROSS JOIN (
SELECT 'DAY' AS type,
INTERVAL '0' HOUR AS start_offset,
INTERVAL '16' HOUR AS end_offset
FROM DUAL
UNION ALL
SELECT 'NIGHT' AS type,
INTERVAL '16' HOUR AS start_offset,
INTERVAL '24' HOUR AS end_offset
FROM DUAL
) o
WHERE L.start_date + o.start_offset < s.enddate
AND L.start_date + o.end_offset > s.startdate;
Which outputs:
ID
TYPE
STARTDATE
ENDDATE
1
DAY
2022-02-03 08:40:00
2022-02-03 22:00:00
1
NIGHT
2022-02-03 22:00:00
2022-02-04 06:00:00
1
DAY
2022-02-04 06:00:00
2022-02-04 10:07:00
2
DAY
2022-02-03 08:40:00
2022-02-03 22:00:00
2
NIGHT
2022-02-03 22:00:00
2022-02-04 02:07:00
3
NIGHT
2022-02-03 04:40:00
2022-02-03 06:00:00
3
DAY
2022-02-03 06:00:00
2022-02-03 22:00:00
3
NIGHT
2022-02-03 22:00:00
2022-02-04 02:07:00
db<>fiddle here
Step 1.
First of all you need to generate all possible intervals. You can do it using simple lateral. To make it easier and more agile, I'll save day shifts in the INTERVALS CTE:
DBFiddle
WITH SAMPLE AS (
SELECT
1 AS ID,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 10:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
UNION ALL
SELECT
2 AS ID,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
UNION ALL
SELECT
3 AS ID,
TO_DATE('2022-02-03 04:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
)
,intervals(i_name,i_start,i_end) as (
select 'Day Shift' ,'0600', '2159' from dual union all
select 'Night Shift','2200', '0559' from dual
)
SELECT
s.*
,days.*
,ints.*
FROM
SAMPLE s
,lateral(
select trunc(startdate) + n as n_day
from xmltable(
'-1 to xs:integer(.)'
passing trunc(trunc(enddate) - trunc(startdate))
columns n int path '.'
)
) days
,lateral(
select
i.*
,to_date(to_char(n_day,'yyyy-mm-dd ')||i_start, 'yyyy-mm-dd hh24mi')
as dtm_start
,to_date(to_char(n_day,'yyyy-mm-dd ')||i_end , 'yyyy-mm-dd hh24mi')
+ case when i_end < i_start then 1 else 0 end -- +1 if it ends on next day
as dtm_end
from intervals i
) ints
order by id,startdate,n_day,dtm_start;
Results:
ID STARTDATE ENDDATE N_DAY I_NAME I_ST I_EN DTM_START DTM_END
--- ------------------- ------------------- ---------- ----------- ---- ---- ------------------- -------------------
1 2022-02-03 08:40:00 2022-02-04 10:07:00 2022-02-02 Day Shift 0600 2159 2022-02-02 06:00:00 2022-02-02 21:59:00
1 2022-02-03 08:40:00 2022-02-04 10:07:00 2022-02-02 Night Shift 2200 0559 2022-02-02 22:00:00 2022-02-03 05:59:00
1 2022-02-03 08:40:00 2022-02-04 10:07:00 2022-02-03 Day Shift 0600 2159 2022-02-03 06:00:00 2022-02-03 21:59:00
1 2022-02-03 08:40:00 2022-02-04 10:07:00 2022-02-03 Night Shift 2200 0559 2022-02-03 22:00:00 2022-02-04 05:59:00
1 2022-02-03 08:40:00 2022-02-04 10:07:00 2022-02-04 Day Shift 0600 2159 2022-02-04 06:00:00 2022-02-04 21:59:00
1 2022-02-03 08:40:00 2022-02-04 10:07:00 2022-02-04 Night Shift 2200 0559 2022-02-04 22:00:00 2022-02-05 05:59:00
2 2022-02-03 08:40:00 2022-02-04 02:07:00 2022-02-02 Day Shift 0600 2159 2022-02-02 06:00:00 2022-02-02 21:59:00
2 2022-02-03 08:40:00 2022-02-04 02:07:00 2022-02-02 Night Shift 2200 0559 2022-02-02 22:00:00 2022-02-03 05:59:00
2 2022-02-03 08:40:00 2022-02-04 02:07:00 2022-02-03 Day Shift 0600 2159 2022-02-03 06:00:00 2022-02-03 21:59:00
2 2022-02-03 08:40:00 2022-02-04 02:07:00 2022-02-03 Night Shift 2200 0559 2022-02-03 22:00:00 2022-02-04 05:59:00
2 2022-02-03 08:40:00 2022-02-04 02:07:00 2022-02-04 Day Shift 0600 2159 2022-02-04 06:00:00 2022-02-04 21:59:00
2 2022-02-03 08:40:00 2022-02-04 02:07:00 2022-02-04 Night Shift 2200 0559 2022-02-04 22:00:00 2022-02-05 05:59:00
3 2022-02-03 04:40:00 2022-02-04 02:07:00 2022-02-02 Day Shift 0600 2159 2022-02-02 06:00:00 2022-02-02 21:59:00
3 2022-02-03 04:40:00 2022-02-04 02:07:00 2022-02-02 Night Shift 2200 0559 2022-02-02 22:00:00 2022-02-03 05:59:00
3 2022-02-03 04:40:00 2022-02-04 02:07:00 2022-02-03 Day Shift 0600 2159 2022-02-03 06:00:00 2022-02-03 21:59:00
3 2022-02-03 04:40:00 2022-02-04 02:07:00 2022-02-03 Night Shift 2200 0559 2022-02-03 22:00:00 2022-02-04 05:59:00
3 2022-02-03 04:40:00 2022-02-04 02:07:00 2022-02-04 Day Shift 0600 2159 2022-02-04 06:00:00 2022-02-04 21:59:00
3 2022-02-03 04:40:00 2022-02-04 02:07:00 2022-02-04 Night Shift 2200 0559 2022-02-04 22:00:00 2022-02-05 05:59:00
Note, that since I have specified time intervals in hhmi (ie hh24mi in oracle datetime format models), we need to ignore seconds.
As you can see lateral(...) days generates all dates between one day before startdate (to cover the end of night shift) and enddate.
Then ints generates day and night shifts for all those days.
Step 2.
So the only thing you need now is to filter them and correct start time and end time of partial intervals.
These 2 predicates filters them:
and ints.dtm_end >= s.startdate
and ints.dtm_start <= s.enddate
and these 2 lines return correct start and end time:
greatest(s.startdate, ints.dtm_start) as startdate,
least (s.enddate , ints.dtm_end ) as enddate,
So full solution: DBFiddle
WITH SAMPLE AS (
SELECT
1 AS ID,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 10:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
UNION ALL
SELECT
2 AS ID,
TO_DATE('2022-02-03 08:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
UNION ALL
SELECT
3 AS ID,
TO_DATE('2022-02-03 04:40', 'YYYY-MM-DD HH24:MI') AS STARTDATE,
TO_DATE('2022-02-04 02:07', 'YYYY-MM-DD HH24:MI') AS ENDDATE
FROM
DUAL
)
,intervals(i_name,i_start,i_end) as (
select 'Day Shift' ,'0600', '2159' from dual union all
select 'Night Shift','2200', '0559' from dual
)
SELECT
s.id,
greatest(s.startdate, ints.dtm_start) as startdate,
least (s.enddate , ints.dtm_end ) as enddate,
i_name,
i_start,
i_end
FROM
SAMPLE s
,lateral(
select trunc(startdate) + n as n_day
from xmltable(
'-1 to xs:integer(.)'
passing trunc(trunc(enddate) - trunc(startdate))
columns n int path '.'
)
) days
,lateral(
select
i.*
,to_date(to_char(n_day,'yyyy-mm-dd ')||i_start, 'yyyy-mm-dd hh24mi')
as dtm_start
,to_date(to_char(n_day,'yyyy-mm-dd ')||i_end , 'yyyy-mm-dd hh24mi')
+ case when i_end < i_start then 1 else 0 end -- +1 if it ends on next day
as dtm_end
from intervals i
) ints
where 1=1
-- filter `ints`:
and ints.dtm_end >= s.startdate
and ints.dtm_start <= s.enddate
order by 1,2,3;
Results:
ID STARTDATE ENDDATE I_NAME I_ST I_EN
---------- ---------------- ---------------- ----------- ---- ----
1 2022-02-03 08:40 2022-02-03 21:59 Day Shift 0600 2159
1 2022-02-03 22:00 2022-02-04 05:59 Night Shift 2200 0559
1 2022-02-04 06:00 2022-02-04 10:07 Day Shift 0600 2159
2 2022-02-03 08:40 2022-02-03 21:59 Day Shift 0600 2159
2 2022-02-03 22:00 2022-02-04 02:07 Night Shift 2200 0559
3 2022-02-03 04:40 2022-02-03 05:59 Night Shift 2200 0559
3 2022-02-03 06:00 2022-02-03 21:59 Day Shift 0600 2159
3 2022-02-03 22:00 2022-02-04 02:07 Night Shift 2200 0559
8 rows selected.
Obviously, you can remove i_start and i_end columns from the output. I showed them just to highlight day/night shift intervals.

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

Splitting time range based on activities in sql

I need a Oracle sql to show the following output given the sample input.
Basically, an employee is schedule for a 9 hour shift.
I need to split up the activities during the day to separate records.
Especially the general activity of Cash. I need to create new records.
Activity start time end time
Shift 2010-01-01 8:00:00 2010-01-01 17:00:00
Open 2010-01-01 8:00:00 2010-01-01 9:00:00
Cash 2010-01-01 9:00:00 2010-01-01 16:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00
OUTPUT:
Activity start time end time
Open 2010-01-01 8:00:00 2010-01-01 9:00:00
Cash 2010-01-01 9:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00
Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00
Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00
Any help is greatly appreciated.
This is a kind of gaps-and-islands problem. Assuming there is always an 'Open' record whose start matches the 'Shift' start, and a 'Close' record whose end matches the 'Shift' end; and that the general activity is always 'Cash' and its start matches the 'Open' end and its end matches the 'Close' start; then some of those records are redundant when filling in the gaps.
You can use the lead and lag functions to generate dummy 'Cash' records that sit between all the other activities, looking both forward and behind:
select activity orig_activity, start_time orig_start, end_time orig_end,
'Cash' as activity, lag(end_time) over (order by end_time) as start_time, start_time as end_time
from table1
where activity not in ('Shift', 'Cash')
union all
select activity orig_activity, start_time orig_start, end_time orig_end,
'Cash' as activity, end_time as start_time, lead(start_time) over (order by start_time) as end_time
from table1
where activity not in ('Shift', 'Cash')
order by orig_start;
ORIG_ ORIG_START ORIG_END ACTI START_TIME END_TIME
----- ------------------- ------------------- ---- ------------------- -------------------
Open 2010-01-01 08:00:00 2010-01-01 09:00:00 Cash 2010-01-01 08:00:00
Open 2010-01-01 08:00:00 2010-01-01 09:00:00 Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00 Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00 Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00 Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00 Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00 Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00 Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00 Cash 2010-01-01 17:00:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00 Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
That has duplicates from the same gap being seen, for instance, after the break and before lunch. By ignoring the original values you can remove those with distinct, or with union instead of union all. You can also exclude any generated rows with null start or end times, and any that overlap with other records - which could happen if two other activities were contiguous:
select activity, start_time, end_time from (
select 'Cash' as activity,
lag(end_time) over (order by end_time) as start_time,
start_time as end_time
from table1
where activity not in ('Shift', 'Cash')
union
select 'Cash' as activity,
end_time as start_time,
lead(start_time) over (order by start_time) as end_time
from table1
where activity not in ('Shift', 'Cash')
) tmp
where start_time is not null
and end_time is not null
and not exists (
select null from table1 where activity not in ('Shift', 'Cash') and (start_time = tmp.start_time or end_time = tmp.end_time)
)
order by start_time;
ACTI START_TIME END_TIME
---- ------------------- -------------------
Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
You can then union that with all the original table rows, except the 'Cash' record:
...
union all
select activity, start_time, end_time
from table1
where activity not in ('Shift', 'Cash')
order by start_time;
ACTIV START_TIME END_TIME
----- ------------------- -------------------
Open 2010-01-01 08:00:00 2010-01-01 09:00:00
Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00
Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00
Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00
This also assumes that activities never overlap, but non-'Cash' activities could be adjacent.
There are probably other gaps-and-islands approaches that would work too.
I'll second Alex's answer. But, just for something completely different, you could figure out the distinct seconds in the shift, figure out what the person was doing each second, then group those into ranges for your results.
I think this would be less efficient than Alex's approach, but might be more flexible: it doesn't assume as much about how the input data will look.
with shift_data ( activity, start_time, end_time ) AS
-- This is just test data that would be in your database table
(
SELECT 'Shift',to_date('2010-01-01 8:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 17:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Open',to_date('2010-01-01 8:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 9:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Cash',to_date('2010-01-01 9:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 16:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Break',to_date('2010-01-01 10:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 10:15:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Lunch',to_date('2010-01-01 12:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 13:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Break',to_date('2010-01-01 14:30:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 14:45:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Close',to_date('2010-01-01 16:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 17:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL
),
seconds_in_shift as (
-- Step 1: get a list of every second that falls in the shift
SELECT start_time + (ROWNUM - 1) / 86400 second
FROM shift_data
WHERE activity = 'Shift'
CONNECT BY ROWNUM <= ( (end_time - start_time) * 86400) + 1),
activity_each_second as (
-- Step 2: figure out what the person was doing every second. If multiple
-- activities overlap, choose whichever one had the shortest duration
-- Also, mark which seconds represent a transition from one activity to
-- another ("marker" column)
SELECT second,
MAX (activity) KEEP (DENSE_RANK FIRST ORDER BY end_time - start_time) activity,
CASE WHEN MAX (activity) KEEP (DENSE_RANK FIRST ORDER BY end_time - start_time)
!= NVL(LAG(MAX (activity) KEEP (DENSE_RANK FIRST ORDER BY end_time - start_time))
OVER ( PARTITION BY NULL ORDER BY SECOND),'#NULL#') THEN 'Y' ELSE NULL END marker
FROM seconds_in_shift ss
INNER JOIN shift_data sd ON ss.second BETWEEN sd.start_time AND sd.end_time
GROUP BY second),
ranges as (
-- Step 3: count the number of marker columns from the beginning of the shift
-- to the current second. Call this "activity_number".
select aes.*,
count(marker) OVER ( PARTITION BY NULL ORDER BY second) activity_number
from activity_each_second aes )
-- Finally, show the activity, start, and end time for each activity_number
SELECT activity,
round(min(second),'MI') start_time,
round(max(second),'MI') end_time
FROM ranges
GROUP BY activity, activity_number
ORDER BY activity_number;
Results:
Open 1/1/2010 8:00:00 A 1/1/2010 9:00:00 AM
Cash 1/1/2010 9:00:00 A 1/1/2010 10:00:00 AM
Break 1/1/2010 10:00:00 1/1/2010 10:15:00 AM
Cash 1/1/2010 10:15:00 1/1/2010 12:00:00 PM
Lunch 1/1/2010 12:00:00 1/1/2010 1:00:00 PM
Cash 1/1/2010 1:00:00 P 1/1/2010 2:30:00 PM
Break 1/1/2010 2:30:00 P 1/1/2010 2:45:00 PM
Cash 1/1/2010 2:45:00 P 1/1/2010 4:00:00 PM
Close 1/1/2010 4:00:00 P 1/1/2010 5:00:00 PM
NOTE: I cheated a bit by rounding the times to the nearest minute. Without rounding, there would be overlap in the ranges. E.g., 4PM on-the-dot would either be "Cash" or "Close", it wouldn't be both.
Assumptions: All activity intervals fall within the 'Shift' interval, and two activity intervals may have at most an endpoint in common (they may be adjacent - but they can't overlap in any way).
Also, I assumed you may have more than one employee in your table (so that must be addressed), and that the computation must be done separately for each calendar day. You will see this in the input data, and handled in the query.
Here is a way to get the desired result using only the analytic lag() function. It first collects only the activities different from 'Shift' and 'Cash', then it fills the gaps with 'Cash' (including at the beginning and/or end of the 'Shift', if no specific activity, like 'Open' or 'Close', starts or ends at the beginning or the end of a 'Shift'). The 'Shift' interval in particular, as presented in the inputs, is not particularly helpful in this solution; you will see how I handle that in the CTE I called prep below.
So I don't need to enter nls_date_format everywhere, I first ran
alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss'
Then:
with
table1 ( empno, activity, start_time, end_time ) as (
select 101, 'Shift', to_date('2010-01-01 8:00:00') , to_date('2010-01-01 17:00:00') from dual union all
select 101, 'Open' , to_date('2010-01-01 8:00:00') , to_date('2010-01-01 9:00:00') from dual union all
select 101, 'Cash' , to_date('2010-01-01 9:00:00') , to_date('2010-01-01 16:00:00') from dual union all
select 101, 'Break', to_date('2010-01-01 10:00:00'), to_date('2010-01-01 10:15:00') from dual union all
select 101, 'Lunch', to_date('2010-01-01 12:00:00'), to_date('2010-01-01 13:00:00') from dual union all
select 101, 'Break', to_date('2010-01-01 14:30:00'), to_date('2010-01-01 14:45:00') from dual union all
select 101, 'Close', to_date('2010-01-01 16:00:00'), to_date('2010-01-01 17:00:00') from dual
),
prep ( empno, activity, start_time, end_time, flag ) as (
select empno, activity, start_time, end_time, 1
from table1
where activity not in ('Shift', 'Cash')
union all select empno, 'Shift', start_time, start_time, 0
from table1
where activity = 'Shift'
union all select empno, 'Shift', end_time, end_time, 2
from table1
where activity = 'Shift'
),
with_cash_intervals ( empno, activity, start_time, end_time ) as (
select empno, activity, start_time, end_time
from prep
where activity != 'Shift'
union all
select empno, 'Cash', lag(end_time) over (partition by empno, trunc(start_time)
order by flag, start_time), start_time
from prep
)
select empno, activity, start_time, end_time
from with_cash_intervals
where start_time < end_time
order by empno, start_time -- if needed
Output:
EMPNO ACTIVITY START_TIME END_TIME
----- -------- ------------------- -------------------
101 Open 2010-01-01 08:00:00 2010-01-01 09:00:00
101 Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
101 Break 2010-01-01 10:00:00 2010-01-01 10:15:00
101 Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
101 Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
101 Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
101 Break 2010-01-01 14:30:00 2010-01-01 14:45:00
101 Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
101 Close 2010-01-01 16:00:00 2010-01-01 17:00:00
9 rows selected.

SQL Multiple record : Time Scheduler

I have problem about combining tables in store procedure.
Note : field "Time" is varchar
First table (tbTime)
Time
08:00:00
08:30:00
09:00:00
09:30:00
10:00:00
10:30:00
11:00:00
11:30:00
12:00:00
12:30:00
13:00:00
13:30:00
14:00:00
14:30:00
15:00:00
15:30:00
16:00:00
16:30:00
17:00:00
17:30:00
18:00:00
18:30:00
19:00:00
19:30:00
20:00:00
Second table (tbClassRsv)
select * from tbclassrsv where transdate='2014-02-05 00:00:00' and status<>'DEL'
transDate time until status studentCode tutor class description userID
2014-02-05 16:48:14 17:48:14 OPN ET-7201 ET-444 ROOM 01 try ADMIN
I want the result with condition schedule like this
Time Student
08:00:00 -
08:30:00 -
09:00:00 -
09:30:00 -
10:00:00 -
10:30:00 -
11:00:00 -
11:30:00 -
12:00:00 -
12:30:00 -
13:00:00 -
13:30:00 -
14:00:00 -
14:30:00 -
15:00:00 -
15:30:00 -
16:00:00 -
16:30:00 ET-7201
17:00:00 ET-7201
17:30:00 ET-7201
18:00:00 ET-7201
18:30:00 -
19:00:00 -
19:30:00 -
20:00:00 -
Thanks for reading or answer ^_^
GBU
I`ve tried this
select t.time,
isnull(
(select c.studentCode
from tbclassrsv c
where c.transdate='2014-02-05 00:00:00'
and c.class='ROOM 01'
and c.status<>'DEL'
and t.time>=c.time
and t.time<=c.until
),'-') [Student]
The result is....
Time Student
08:00:00 -
08:30:00 -
09:00:00 -
09:30:00 -
10:00:00 -
10:30:00 -
11:00:00 -
11:30:00 -
12:00:00 -
12:30:00 -
13:00:00 -
13:30:00 -
14:00:00 -
14:30:00 -
15:00:00 -
15:30:00 -
16:00:00 -
16:30:00 -
17:00:00 ET-7201
17:30:00 ET-7201
18:00:00 -
18:30:00 -
19:00:00 -
19:30:00 -
20:00:00 -
Try this. you were not converting your varchar times to datetime so that your time comparisons would work.
select t.time,
isnull(
(select c.studentCode
from tbClassRsv c
where c.transdate='2014-02-05 00:00:00'
and c.class='ROOM 01'
and c.status<>'DEL'
and DateAdd(MINUTE, 30, Convert(datetime, t.time))>= Convert(datetime, c.time)
and Convert(datetime, t.time) <= Convert(datetime, c.until)
),'-') from [tbTime] t
What you need to do is round c.time down to the nearest 30 minutes interval, and round c.until up to the nearest interval. This way your where clause will have the correct range.
To do the rounding you will need to convert the times to datetime which you can do like so:
CAST(CONVERT(varchar,THE_TIME_AS_VARCHAR,121) AS datetime)
Then you can round down to the nearest 30 minutes like so:
DATEADD(mi, DATEDIFF(mi, 0, THE_TIME_AS_DATETIME)/30*30, 0)
And round up like so:
DATEADD(mi, DATEDIFF(mi, 30, THE_TIME_AS_DATETIME)/30*30, 0)
Applying all that to your existing code would give you this:
select t.time,
isnull(
(select c.studentCode
from tbclassrsv c
where c.transdate='2014-02-05 00:00:00'
and c.class='ROOM 01'
and c.status<>'DEL'
and t.time>= DATEADD(mi, DATEDIFF(mi, 0, CAST(CONVERT(varchar,c.time,121) AS datetime))/30*30, 0)
and t.time<= DATEADD(mi, DATEDIFF(mi, 30, CAST(CONVERT(varchar,c.until,121) AS datetime))/30*30, 0)
),'-') [Student]