Tyvain
Tyvain

Reputation: 2750

Oracle find gaps split by allowed slots

I have this table of calendar gaps (for a report):

begindate               enddate 
2017-12-14 16:45:00     2017-12-14 21:45:00
2017-12-15 17:45:00     2017-12-16 10:00:00

The second line range on 2 different days.

I want to split it with given 'allowed' time slots, gaps can only be between 7am and 8pm (20:00:00).

So the result should be 3 lines:

begindate               enddate 
2017-12-14 16:45:00     2017-12-14 20:00:00
2017-12-15 17:45:00     2017-12-15 20:00:00
2017-12-16 07:00:00     2017-12-16 10:00:00

How can I do that in sql (oracle function allowed).

Upvotes: 0

Views: 29

Answers (1)

Del
Del

Reputation: 1599

This was an interesting one, here is my answer:

WITH test_data (beginDate, endDate) AS
(
  SELECT TO_DATE('2017-12-14 16:45:00', 'YYYY-MM-DD HH24:MI:SS'),
         TO_DATE('2017-12-14 21:45:00', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL
  UNION ALL
  SELECT TO_DATE('2017-12-15 17:45:00', 'YYYY-MM-DD HH24:MI:SS'),
         TO_DATE('2017-12-16 10:00:00', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL
  UNION ALL
  SELECT TO_DATE('2017-12-15 01:45:00', 'YYYY-MM-DD HH24:MI:SS'),
         TO_DATE('2017-12-15 06:00:00', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL
),
split_dates (beginDate, endDate, actEnd, remaining, lvl) AS
(
  SELECT beginDate, LEAST(endDate, TRUNC(beginDate)+1), endDate, TRUNC(endDate) - TRUNC(beginDate), 1
  FROM test_data
  UNION ALL
  SELECT TRUNC(beginDate)+lvl, LEAST(actEnd, TRUNC(beginDate)+lvl+1), actEnd, remaining-1, lvl+1
  FROM split_dates sd
  WHERE sd.remaining > 0
)
SELECT TO_CHAR(GREATEST(sd.beginDate, TRUNC(sd.beginDate)+7/24), 'YYYY-MM-DD HH24:MI:SS') AS beginDate,
       TO_CHAR(LEAST(sd.endDate, TRUNC(sd.beginDate)+5/6), 'YYYY-MM-DD HH24:MI:SS') AS endDate
FROM split_dates sd
WHERE GREATEST(sd.beginDate, TRUNC(sd.beginDate)+7/24) <= LEAST(sd.endDate, TRUNC(sd.beginDate)+5/6);

The problem is two-fold:

  1. You need to split multi-day records in separate rows. I accomplished this with the split_records CTE.
  2. You need to overlay your valid times on the calculated splits and check that the new times are valid.

I created a DBFiddle to show you the query in action (Link)

Upvotes: 1

Related Questions