TSQL - 没有重叠的连续调度的岛和间隙

时间:2017-03-01 14:53:51

标签: sql tsql gaps-and-islands

我再一次遇到关于时间表输出的岛屿和间隙问题,我们将非常感谢一些帮助。

在我的示例中,我有包含计划容器(intType = 1)和计划部分(intType = 0)的用户计划列表,并且我已经被要求弄清楚如何传递连续的计划而不是重复的风格我多年来一直在这个系统中使用。目前 MOST 计划部分属于Schedule容器的开始和结束时间,但一个代码(intCode = 32)除外,它在开始和结束时都有一个开始时间和结束时间。任何两个计划容器,如下例所示。

DECLARE @Schedule TABLE (
intUserID BIGINT,
dtDate DATETIME,
dtStart DATETIME,
dtStop DATETIME,
intCode INT,
intType INT);

INSERT INTO @Schedule
   ([dtDate]
   ,[intUserID]
   ,[dtStart]
   ,[dtStop]
   ,[intCode]
   ,[intType])
select '2017-02-23 00:00:00',   444444444444,   '2017-02-23 10:00:00.000',  '2017-02-23 19:00:00.000',  46, 1
union 
select '2017-02-23 00:00:00',   444444444444,   '2017-02-23 12:00:00.000',  '2017-02-23 12:15:00.000',  66, 0
union 
select '2017-02-23 00:00:00',   444444444444,   '2017-02-23 12:20:00.000',  '2017-02-23 12:26:00.000',  110,0
union
SELECT '2017-02-23 00:00:00',   444444444444,   '2017-02-23 14:00:00.000',  '2017-02-23 15:00:00.000',  76, 0
union 
SELECT '2017-02-23 00:00:00',   444444444444,   '2017-02-23 17:00:00.000',  '2017-02-23 17:15:00.000',  66, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 10:00:00.000',  '2017-02-22 19:00:00.000',  46, 1
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 12:00:00.000',  '2017-02-22 12:15:00.000',  66, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 14:00:00.000',  '2017-02-22 15:00:00.000',  76, 0
union
select '2017-02-22 00:00:00',   888888888888,   '2017-02-22 17:00:00.000',  '2017-02-22 17:15:00.000',  66, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 19:00:00.000',  '2017-02-22 21:00:00.000',  32, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 21:00:00.000',  '2017-02-22 21:30:00.000',  46, 1
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 21:00:00.000',  '2017-02-22 21:30:00.000',  59, 0


SELECT * FROM @Schedule ORDER BY intUserID, dtDate, dtStart, intType DESC

这为我们提供了当前的计划存储数据,如下所示:

intUserID    dtDate           dtStart          dtStop           intCode intType
------------ ---------------- ---------------- ---------------- ------- -------
444444444444 2017-02-23 00:00 2017-02-23 10:00 2017-02-23 19:00      46       1
444444444444 2017-02-23 00:00 2017-02-23 12:00 2017-02-23 12:15      66       0
444444444444 2017-02-23 00:00 2017-02-23 12:20 2017-02-23 12:26     110       0
444444444444 2017-02-23 00:00 2017-02-23 14:00 2017-02-23 15:00      76       0
444444444444 2017-02-23 00:00 2017-02-23 17:00 2017-02-23 17:15      66       0
888888888888 2017-02-22 00:00 2017-02-22 10:00 2017-02-22 19:00      46       1
888888888888 2017-02-22 00:00 2017-02-22 12:00 2017-02-22 12:15      66       0
888888888888 2017-02-22 00:00 2017-02-22 14:00 2017-02-22 15:00      76       0
888888888888 2017-02-22 00:00 2017-02-22 17:00 2017-02-22 17:15      66       0
888888888888 2017-02-22 00:00 2017-02-22 19:00 2017-02-22 21:00      32       0
888888888888 2017-02-22 00:00 2017-02-22 21:00 2017-02-22 21:30      46       1
888888888888 2017-02-22 00:00 2017-02-22 21:00 2017-02-22 21:30      59       0

新请求是在计划的原始开始和结束时间之间的所有时间填充计划容器,其中没有计划部分,或者看起来像这样:

intUserID    dtDate           dtStart          dtStop           intCode intType
------------ ---------------- ---------------- ---------------- ------- -------
444444444444 2017-02-23 00:00 2017-02-23 10:00 2017-02-23 12:00      46       1
444444444444 2017-02-23 00:00 2017-02-23 12:00 2017-02-23 12:15      66       0
444444444444 2017-02-23 00:00 2017-02-23 12:15 2017-02-23 12:20      46       1
444444444444 2017-02-23 00:00 2017-02-23 12:20 2017-02-23 12:26     110       0
444444444444 2017-02-23 00:00 2017-02-23 12:26 2017-02-23 14:00      46       1
444444444444 2017-02-23 00:00 2017-02-23 14:00 2017-02-23 15:00      76       0
444444444444 2017-02-23 00:00 2017-02-23 15:00 2017-02-23 17:00      46       1
444444444444 2017-02-23 00:00 2017-02-23 17:00 2017-02-23 17:15      66       0
444444444444 2017-02-23 00:00 2017-02-23 17:15 2017-02-23 19:00      46       1
888888888888 2017-02-22 00:00 2017-02-22 10:00 2017-02-22 12:00      46       1
888888888888 2017-02-22 00:00 2017-02-22 12:00 2017-02-22 12:15      66       0
888888888888 2017-02-22 00:00 2017-02-22 12:15 2017-02-22 14:00      46       1
888888888888 2017-02-22 00:00 2017-02-22 14:00 2017-02-22 15:00      76       0
888888888888 2017-02-22 00:00 2017-02-22 15:00 2017-02-22 17:00      46       1
888888888888 2017-02-22 00:00 2017-02-22 17:00 2017-02-22 17:15      66       0
888888888888 2017-02-22 00:00 2017-02-22 17:15 2017-02-22 19:00      46       1
888888888888 2017-02-22 00:00 2017-02-22 19:00 2017-02-22 21:00      32       0
888888888888 2017-02-22 00:00 2017-02-22 21:00 2017-02-22 21:30      59       0

正如您所看到的那样,区别在于第二个场景中的计划容器变为" Water"填写计划部分之间的时间,边界等于容器的原始开始和结束时间。

我希望我能够充分解释,或者如果没有,预期的输出将帮助某人制定一些可以帮助我完成这项工作而不会遍历所有时间表的东西,因为其中有数千个输出和速度是必要的。

提前感谢您提供的任何帮助!

1 个答案:

答案 0 :(得分:0)

我认为答案在于设定时间表的边界并填补空白,看来我是对的。如果有人有兴趣仔细检查我的工作,我使用的解决方案如下:

DECLARE @Schedule TABLE (
intUserID BIGINT,
dtDate DATETIME,
dtStart DATETIME,
dtEnd DATETIME,
intCode INT,
intType INT);

INSERT INTO @Schedule
   ([dtDate]
   ,[intUserID]
   ,[dtStart]
   ,[dtEnd]
   ,[intCode]
   ,[intType])
select '2017-02-23 00:00:00',   444444444444,   '2017-02-23 10:00:00.000',  '2017-02-23 19:00:00.000',  46, 1
union 
select '2017-02-23 00:00:00',   444444444444,   '2017-02-23 12:00:00.000',  '2017-02-23 12:15:00.000',  66, 0
union 
select '2017-02-23 00:00:00',   444444444444,   '2017-02-23 12:20:00.000',  '2017-02-23 12:26:00.000',  110,0
union
SELECT '2017-02-23 00:00:00',   444444444444,   '2017-02-23 14:00:00.000',  '2017-02-23 15:00:00.000',  76, 0
union 
SELECT '2017-02-23 00:00:00',   444444444444,   '2017-02-23 17:00:00.000',  '2017-02-23 17:15:00.000',  66, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 10:00:00.000',  '2017-02-22 19:00:00.000',  46, 1
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 12:00:00.000',  '2017-02-22 12:15:00.000',  66, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 14:00:00.000',  '2017-02-22 15:00:00.000',  76, 0
union
select '2017-02-22 00:00:00',   888888888888,   '2017-02-22 17:00:00.000',  '2017-02-22 17:15:00.000',  66, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 19:00:00.000',  '2017-02-22 21:00:00.000',  32, 0
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 21:00:00.000',  '2017-02-22 21:30:00.000',  46, 1
union
select  '2017-02-22 00:00:00',  888888888888,   '2017-02-22 21:00:00.000',  '2017-02-22 21:30:00.000',  59, 0




DECLARE @tmpSchedule TABLE (
dtDate datetime
,dtStart datetime
,dtEnd datetime
,intCode int
,intType int
,intUserID bigint
)

DECLARE @tmpBaseCodes TABLE (
dtDate datetime
,intCode int
,intUserID bigint null
)

insert into @tmpSchedule
-- Create schedule boundaries
select dtDate, dtStart, dtStart dtEnd, intCode, intType, intUserID from @Schedule where inttype = 1
union
select dtDate, dtEnd dtStart, dtEnd, intCode, intType, intUserID from @Schedule where intType = 1
union
select dtDate, dtStart, dtEnd, intCode, intType, intUserID from @Schedule where inttype = 0

insert into @tmpBaseCodes
select dtDate, min(intCode) intCode, intUserID from @tmpSchedule where intType = 1 group by dtDate, intUserID;
-- Calculate gaps and apply base code between them
WITH C1 AS (
        SELECT dtDate, intUserID, ts, Type
            ,e=CASE Type WHEN 1 THEN NULL ELSE ROW_NUMBER() OVER (PARTITION BY dtDate,intUserID, Type ORDER BY dtEnd) END
            ,s=CASE Type WHEN -1 THEN NULL ELSE ROW_NUMBER() OVER (PARTITION BY dtDate,intUserID, Type ORDER BY dtStart) END
        FROM @tmpSchedule
        CROSS APPLY (
            VALUES (1, dtStart), (-1, dtEnd)) a(Type, ts)
        ),
    C2 AS (
        SELECT C1.*
            ,se=ROW_NUMBER() OVER (PARTITION BY dtDate,intUserID ORDER BY ts, Type DESC)
        FROM C1),
    C3 AS (
        SELECT dtDate,intUserID, ts
            ,grpnm=FLOOR((ROW_NUMBER() OVER (PARTITION BY dtDate, intUserID ORDER BY ts)-1) / 2 + 1)
        FROM C2
        WHERE COALESCE(s-(se-s)-1, (se-e)-e) = 0),
    -- C1, C2, C3, C4 combined remove the overlapping date periods
    C4 AS (
        SELECT dtDate, intUserID, dtStart=MIN(ts), dtEnd=MAX(ts)
        FROM C3
        GROUP BY dtDate, intUserID, grpnm)
INSERT INTO @tmpSchedule
(dtdate, dtstart, dtend, intCode, intType, intUserID)
SELECT a.dtDate, dtStart=MIN(newdate), dtEnd=MAX(newdate), b.intCode, 1 intType, a.intUserID
FROM (
    SELECT dtDate, newdate, intUserID
        ,rn=ROW_NUMBER() OVER (PARTITION BY dtDate,intUserID ORDER BY newdate) / 2
    FROM C4 a
    CROSS APPLY (
        VALUES (dtStart),(dtEnd)) b(newdate)
    ) a
JOIN @tmpBaseCodes b on b.dtDate = a.dtDate and b.intUserID = a.intUserID
GROUP BY a.dtDate, a.intUserID, b.intCode, rn
HAVING COUNT(*) = 2
ORDER BY intUserID, dtDate, dtStart;

-- Select the entries from the schedule except boundaries
select * from @tmpSchedule
where dtStart != dtEnd
order by intUserID,dtDate, dtStart, intType desc