根据SQL

时间:2017-07-23 09:21:41

标签: sql sql-server tsql date-range timeslots

我有一个包含多个时间窗口的数据集,一个可用性指标和一个优先级索引。

创建数据集

CREATE TABLE TimeWindows(
TimeFrom DATETIME NOT NULL,
TimeTo DATETIME NOT NULL,
Priority INT NOT NULL,
Available BIT NOT NULL
);

INSERT INTO TimeWindows(TimeFrom, TimeTo, Priority, Available) VALUES
('2017-07-22 07:00:00', '2017-07-22 12:00:00', 1, 1),
('2017-07-22 13:00:00', '2017-07-22 17:00:00', 1, 1),
('2017-07-22 12:30:00', '2017-07-23 00:00:00', 3, 0),
('2017-07-23 07:00:00', '2017-07-23 12:00:00', 1, 1),
('2017-07-23 13:00:00', '2017-07-23 17:00:00', 1, 1),
('2017-07-23 00:00:00', '2017-07-24 00:00:00', 2, 0),
('2017-07-23 19:00:00', '2017-07-23 20:00:00', 4, 1),
('2017-07-24 07:00:00', '2017-07-24 12:00:00', 1, 1),
('2017-07-24 13:00:00', '2017-07-24 17:00:00', 1, 1),
('2017-07-24 15:00:00', '2017-07-24 16:00:00', 4, 0);

示例数据集:

| TimeFrom            | TimeTo              | Priority | Available |
|---------------------|---------------------|----------|-----------|
| 2017-07-22 07:00:00 | 2017-07-22 12:00:00 |        1 |         1 |
| 2017-07-22 13:00:00 | 2017-07-22 17:00:00 |        1 |         1 |
| 2017-07-22 12:30:00 | 2017-07-23 00:00:00 |        3 |         0 |
| 2017-07-23 07:00:00 | 2017-07-23 12:00:00 |        1 |         1 |
| 2017-07-23 13:00:00 | 2017-07-23 17:00:00 |        1 |         1 |
| 2017-07-23 00:00:00 | 2017-07-24 00:00:00 |        2 |         0 |
| 2017-07-23 19:00:00 | 2017-07-24 20:00:00 |        4 |         1 |
| 2017-07-24 07:00:00 | 2017-07-24 12:00:00 |        1 |         1 |
| 2017-07-24 13:00:00 | 2017-07-24 17:00:00 |        1 |         1 |
| 2017-07-24 15:00:00 | 2017-07-24 16:00:00 |        4 |         0 |

问题:

我想生成一组新的时间窗口,它只代表可用的时间段。

业务规则:

  • 具有较高优先级的时间窗口将取代具有较低优先级的冲突时间窗口
  • 可用性指示器设置为1(如果可用),如果不可用则设置为0
  • 需要合并重叠时间窗口
  • 一个可用的时间窗口,它被拆分为'一段时间不可用,将被转换为两个单独的可用时间窗口

期望的结果:

| TimeFrom            | TimeTo              |
|---------------------|---------------------|
| 2017-07-22 07:00:00 | 2017-07-22 12:00:00 |
| 2017-07-23 19:00:00 | 2017-07-23 20:00:00 |
| 2017-07-24 07:00:00 | 2017-07-24 12:00:00 |
| 2017-07-24 13:00:00 | 2017-07-24 15:00:00 |
| 2017-07-24 16:00:00 | 2017-07-24 17:00:00 |

有人可以告诉我如何在SQL中解决这个问题吗?

提前感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

嗯,自从我解决了一个很好的SQL挑战以来已经有一段时间了,所以我为你做了很多努力。使用带有一些case语句的CTE和带有更多case语句的另一个查询来获取所有商业规则但只有一个 -

  

需要合并重叠的时间窗口。

但这真的很容易。您会在代码中看到很多解释逻辑的注释,当然,我只能针对您的示例数据进行测试,但它应该让您开始。

可能还有其他更好的解决方案,但这就是我想出来的:

;WITH CTE AS
(

SELECT  t0.TimeFrom As AvailableFrom,
        t0.TimeTo As AvailableTo,
        t1.TimeFrom As UnavailableFrom,
        t1.TimeTo As UnavailableTo,
        CASE WHEN t1.Available IS NULL THEN 
            -- no overlapping records with higher priority and available = 0, use t0 start and end.
            1 -- t0.TimeFrom, t0.TimeTo
        ELSE
            CASE 
                WHEN t0.TimeFrom > t1.TimeFrom AND t0.TimeTo < t1.TimeTo THEN
                -- t0 is inside t1, record of t0 can't be used.
                0
                WHEN t0.TimeFrom < t1.TimeFrom AND t0.TimeTo < t1.TimeTo THEN
                -- t0 starts before t1 starts, and also ends before t1 ends. this means that the start will be t0 start.
                2 -- t0.TimeFrom, t1.TimeFrom
                WHEN t0.TimeFrom > t1.TimeFrom AND t0.TimeTo > t1.TimeTo THEN
                -- t0 starts after t1, but also ends after it. this means that the start will be t1 end.
                3 -- t1.TimeTo, t0.TimeTo
                WHEN t0.TimeFrom < t1.TimeFrom AND t0.TimeTo > t1.TimeTo THEN
                -- t1 is inside t0, need to create 2 records for this.
                4 -- 2 records - t0.TimeFrom, t1.TimeFrom and also t1.TimeTo and t0.TimeTo
            END
        END As RecordType
FROM TimeWindows t0
LEFT JOIN TimeWindows t1 ON t0.TimeFrom <= t1.TimeTo -- t1 overlaps t0
                        AND t0.TimeTo >= t1.TimeFrom -- t1 overlaps t0
                        AND t0.Priority < t1.Priority -- t1 priority is higher than t0 priority
                        AND t1.Available = 0 -- t1 records are unavaialbe
WHERE t0.Available = 1 -- t0 records are available
-- t0 holds all the available time slots, 
-- while t1 holds all the unavailable time slots that overlap t0 records and have a higher priority. (otherwise they don't matter...)
)

SELECT  CASE RecordType
            WHEN 1 THEN
                AvailableFrom
            WHEN 2 THEN
                AvailableFrom
            WHEN 3 THEN
                UnavailableTo
            WHEN 4 THEN
            AvailableFrom
        END As TimeFrom,
        CASE RecordType
            WHEN 1 THEN
                AvailableTo
            WHEN 2 THEN
                UnavailableFrom
            WHEN 3 THEN
                AvailableTo
            WHEN 4 THEN
            UnavailableFrom
        END As TimeTo
FROM CTE 
WHERE RecordType > 0
UNION ALL
SELECT  UnavailableTo,
        AvailableTo
FROM CTE 
WHERE RecordType =4 

结果:

TimeFrom                TimeTo
22.07.2017 07:00:00     22.07.2017 12:00:00
23.07.2017 19:00:00     23.07.2017 20:00:00
24.07.2017 07:00:00     24.07.2017 12:00:00
24.07.2017 13:00:00     24.07.2017 15:00:00
24.07.2017 16:00:00     24.07.2017 17:00:00

You can see a live demo on rextester.