Scenario:
Appointments are made with start and end times. Appointments are allowed to overlap, but for reporting purposes, we need to know when there are overlapping appointments (called overbooking) and when there are not. Two or more appointments may overlap with each other, but there's always one appointment involved in the overbooking that is not classified as overbooked.
Rules:
1. Overlapping portions (segments) of appointments are considered overbooked, except for one of the occurrences of the overlap, which is NOT considered overbooked.
2. The occurrence of the overlap that is chosen to not be considered overbooked does NOT matter; it can be any of the overlaps in the set.
Sample Data:
Appt ID | Appt Start | Appt End |
---|---|---|
1 | 2:00 | 5:00 |
2 | 3:00 | 6:00 |
3 | 2:00 | 6:00 |
4 | 6:00 | 7:00 |
Desired Results:
Appt ID | Classification | Segment Start | Segment End |
---|---|---|---|
1 | NOT OVERBOOKED | 2:00 | 5:00 |
2 | OVERBOOKED | 3:00 | 5:00 |
2 | NOT OVERBOOKED | 5:00 | 6:00 |
3 | OVERBOOKED | 2:00 | 6:00 |
4 | NOT OVERBOOKED | 6:00 | 7:00 |
...or...
Appt ID | Classification | Segment Start | Segment End |
---|---|---|---|
1 | NOT OVERBOOKED | 2:00 | 5:00 |
2 | OVERBOOKED | 3:00 | 6:00 |
3 | OVERBOOKED | 2:00 | 5:00 |
3 | NOT OVERBOOKED | 5:00 | 6:00 |
4 | NOT OVERBOOKED | 6:00 | 7:00 |
etc.
What I've tried:
DROP TABLE IF EXISTS #appts
CREATE TABLE #appts
(
appt_id INT
,st TIME
,et TIME
)
INSERT INTO #appts (appt_id,st,et) VALUES (1,'2:00','5:00')
INSERT INTO #appts (appt_id,st,et) VALUES (2,'3:00','6:00')
INSERT INTO #appts (appt_id,st,et) VALUES (3,'2:00','6:00')
INSERT INTO #appts (appt_id,st,et) VALUES (4,'6:00','7:00')
;With a1 AS
(
SELECT * FROM #appts
)
,a2 AS
(
SELECT * FROM a1
)
SELECT DISTINCT
a1.appt_id
,a2.appt_id
,ost = CASE WHEN a2.st > a1.st THEN a2.st ELSE a2.st END
,oet = CASE WHEN a2.et < a1.et THEN a2.et ELSE a1.et END
FROM
a1
INNER JOIN a2
ON (a1.st > a2.st
AND a1.st < a2.et
OR a1.et > a2.st
AND a1.et < a2.et)
WHERE
a1.appt_id < a2.appt_id
appt_id | appt_id | ost | est |
---|---|---|---|
1 | 2 | 3:00 | 5:00 |
1 | 3 | 2:00 | 5:00 |
2 | 3 | 2:00 | 6:00 |
This does not get me one of the acceptable result sets. It appears to give me the possible overbookings, but I need it to also give me the "not overbooked", allowing for one of the overbookings in each overlap to count as not overbooked.
For MYSQL and comp.:
with Appointments(Appt_ID, Appt_Start, Appt_End) as (
select 1, cast('2:00' as time), cast('5:00' as time) union all
select 2, cast('3:00' as time), cast('6:00' as time) union all
select 3, cast('2:00' as time), cast('6:00' as time) union all
select 4, cast('6:00' as time), cast('7:00' as time)
)
, slices(Appt_Start, Appt_End) as (
select Appt_Start, Appt_End from (
select dt as Appt_Start, lead(dt) over(order by dt, typ) as Appt_End
from (
select Appt_Start as dt, 1 as typ from Appointments
union all
select Appt_End, -1 from Appointments
) d
) d
where Appt_Start < Appt_End
)
select Appt_ID, classification, min(segment_Start) as segment_Start, max(segment_end) as segment_end
from (
select Appt_ID,
case
when rn_by_slice = 1 then 'NOT OVERBOOKED'
else 'OVERBOOKED' end as classification,
segment_Start, segment_end
from (
select d.Appt_ID, s.Appt_Start as segment_Start, s.Appt_End as segment_End,
d.Appt_Start, d.Appt_End,
row_number() over(partition by s.Appt_Start, s.Appt_End order by d.Appt_ID) as rn_by_slice
from slices s
join Appointments d
on least(d.Appt_End - INTERVAL '1' SECOND, s.Appt_End - INTERVAL '1' SECOND) >= greatest(d.Appt_Start, s.Appt_Start)
) d
) d
group by Appt_ID, classification
order by Appt_ID, segment_Start, segment_End
;