在T-SQL中查找最大时间重叠

时间:2015-03-24 13:45:17

标签: sql sql-server tsql sql-server-2008-r2

我试图在SQL Server 2008 R2上执行此操作。

我有一个包含4列的表格:

parent_id INT
child_id INT
start_time TIME
end_time TIME

您应该将子项视为为父程序运行的子进程。所有这些子流程每天运行一次,每个子流程在给定的时间跨度内运行。我想根据子节点的时间找到每个父节点的最大时间间隔重叠,即我想知道所有子进程运行的最长可能重叠。每个时间跨度每天重复的事实意味着即使孩子的时间间隔跨越午夜(即23:00-10:00),它也可以与仅在早晨运行的孩子重叠(即07: 00-09:00),因为即使他们在第一天"上没有重叠,他们也会在随后的所有日子里重叠。

输出应如下所示:

parent_id INT
start_time TIME
end_time TIME
valid BIT

valid = 1如果找到重叠,valid = 0如果未找到重叠,则为<{1}}。

一些重要的信息:

  1. 时间间隔可以跨越午夜,即start_time = 23:00end_time = 03:00,时间间隔为4小时。
  2. 两个时间间隔可能在两个不同的地方重叠,即start_time1 = 13:00end_time1 = 06:00start_time2 = 04:00end_time2 = 14:00。这将是04:00 - 06:00 = 2小时的最大重叠。
  3. 给定父母的子女可能没有共同的重叠,在这种情况下,该父母的外线将是start_time = NULLend_time = NULLvalid = 0
  4. 如果儿童时间间隔为一整天,则为start_time = NULLend_time = NULL。选择此选项是为了避免在00:00-24:00之间设置一天,这将在午夜两次切换重叠,即下面的父3将最终有两个重叠(23:00-24:00和00:00 - 004 :00),而不是一个(23:00-04:00)。
  5. 如果父母的所有孩子共享时间间隔,则重叠只是重叠。
  6. 一个孩子的时间跨度永远不会超过24小时。
  7. 举个例子:

    parent_id  child_id  start_time  end_time
        1         1           06:00     14:00
        1         2           13:00     09:00
        1         3           07:00     09:00
        2         1           12:00     17:00
        2         2           09:00     11:00
        3         1            NULL      NULL
        3         2           23:00     04:00
        4         1            NULL      NULL
        4         2            NULL      NULL
       10         1           06:11     14:00
       10         2           06:00     09:00
       10         3           05:00     08:44
       11         1           11:38     17:00
       11         2           09:02     12:11
    

    这些数据会生成此结果集:

    parent_id  start_time  end_time  valid
        1           07:00     09:00    1
        2            NULL      NULL    0
        3           23:00     04:00    1
        4            NULL      NULL    1
       10           06:11     08:44    1
       11           11:38     12:11    1
    

    父级的重叠是其所有子级共享的时间间隔。因此,通过找到所有3个孩子共享时间的重叠来找到父10的重叠: 孩子1(06:11-14:00)和2(06:00-09:00)从06:11到09:00重叠。然后将这个重叠时间间隔应用于子3(05:00-08:44),这给出了06:11到08:44的重叠,因为这个间隔是所有3个孩子共享公共时间的唯一间隔。

    我希望这是有道理的。

    我可以用光标做到,但我真的更愿意避免使用游标。在没有游标的情况下,我一直在喋喋不休地谈论如何做到这一点,但我已经做得很短。有没有游标的方法吗?

    编辑:扩展第4条的文本,解释将一整天假为NULL的决定,而不是00:00到00:00。 编辑:用另外两个案例扩展了这些例子。新案例的父母ID为10和11。 编辑:插入有关如何找到父10的重叠的说明。 编辑:澄清条款3.增加了第5和第6条。详细了解这是什么。

2 个答案:

答案 0 :(得分:2)

根据您的问题,我认为您的输出应为:

parent_id   start_time  end_time    valid
1           07:00       09:00       1
2           NULL        NULL        0
3           23:00       04:00       1
4           NULL        NULL        1
10          06:11       08:44       1
11          11:38       12:11       1

这是一个基于集合的解决方案:

DECLARE @Times TABLE
(
    parent_id INT
    ,child_id INT
    ,start_time TIME
    ,end_time TIME
);

INSERT INTO @Times
VALUES
    (1,         1,           '06:00',     '14:00')
    ,(1,         2,           '13:00',     '09:00')
    ,(1,         3,           '07:00',     '09:00')
    ,(2,         1,           '12:00',     '17:00')
    ,(2,         2,           '09:00',     '11:00')
    ,(3,         1,            NULL,      NULL)
    ,(3,         2,           '23:00',     '04:00')
    ,(4,         1,            NULL,      NULL)
    ,(4,         2,            NULL,      NULL)
    ,(10,         1,           '06:11',     '14:00')
    ,(10,         2,           '06:00',     '09:00')
    ,(10,         3,           '05:00',     '08:44')
    ,(11,         1,           '11:38',     '17:00')
    ,(11,         2,           '09:02',     '12:11');


DECLARE @Parents TABLE
(
    parent_id INT PRIMARY KEY
    ,ChildCount INT
)
INSERT INTO @Parents
SELECT 
    parent_id
    ,COUNT(DISTINCT child_id) AS ChildCount
FROM
    @Times
GROUP BY 
    parent_id

DECLARE @StartTime DATETIME2 = '00:00'
DECLARE @MinutesInTwoDays INT = 2880
DECLARE @Minutes TABLE(ThisMinute DATETIME2 PRIMARY KEY);

WITH 
MinutesCTE AS
(
    SELECT 
        1 AS MinuteNumber
        ,@StartTime AS ThisMinute

    UNION ALL

    SELECT 
        NextMinuteNumber
        ,NextMinute
    FROM MinutesCTE
    CROSS APPLY (VALUES(MinuteNumber+1,DATEADD(MINUTE,1,ThisMinute))) NextDates(NextMinuteNumber,NextMinute)
    WHERE 
        NextMinuteNumber <= @MinutesInTwoDays
)
INSERT INTO @Minutes
SELECT ThisMinute FROM MinutesCTE M OPTION (MAXRECURSION 2880);


DECLARE @SharedMinutes TABLE
(
    ThisMinute DATETIME2 
    ,parent_id INT
    ,UNIQUE(ThisMinute,parent_id)
);

WITH TimesCTE AS
(
    SELECT
        Times.parent_id
        ,Times.child_id
        ,CAST(ISNULL(Times.start_time,'00:00') AS datetime2) AS start_time
        ,
        DATEADD
        (   
            DAY
            ,
            CASE 
                WHEN Times.end_time IS NULL THEN 2
                WHEN Times.start_time > Times.end_time THEN 1
                ELSE 0 
            END
            ,CAST(ISNULL(Times.end_time,'00:00') AS datetime2)
        ) as end_time
    FROM
        @Times Times


    UNION ALL

    SELECT
        Times.parent_id
        ,Times.child_id
        ,DATEADD(DAY,1,CAST(Times.start_time as datetime2)) AS start_time
        ,DATEADD(DAY,1,CAST(Times.end_time AS datetime2)) AS end_time
    FROM
        @Times Times
    WHERE
        start_time < end_time

)

--Get minutes shared by all children of each parent
INSERT INTO @SharedMinutes
SELECT 
    M.ThisMinute
    ,P.parent_id
FROM
    @Minutes M
JOIN
    TimesCTE T
    ON 
        M.ThisMinute BETWEEN start_time AND end_time
JOIN
    @Parents P
    ON T.parent_id = P.parent_id

GROUP BY 
    M.ThisMinute
    ,P.parent_id
    ,P.ChildCount
HAVING
    COUNT(DISTINCT T.child_id) = P.ChildCount

--get results
SELECT
    parent_id
    ,CAST(CASE WHEN start_time = '1900-01-01' AND end_time = '1900-01-02 23:59' THEN NULL ELSE start_time END AS TIME) AS start_time
    ,CAST(CASE WHEN start_time = '1900-01-01' AND end_time = '1900-01-02 23:59' THEN NULL ELSE end_time END AS TIME) AS end_time
    ,valid
FROM
(
    SELECT
        P.parent_id
        ,MIN(ThisMinute) AS start_time
        ,MAX(ThisMinute) AS end_time
        ,CASE WHEN MAX(ThisMinute) IS NOT NULL THEN 1 ELSE 0 END AS valid 
    FROM
        @Parents P
    LEFT JOIN
        @SharedMinutes SM
        ON P.parent_id = SM.parent_id
    GROUP BY
        P.parent_id

) Results

您可能会发现您在问题中概述的迭代算法会更有效。但是如果采用这种方法,我会使用WHILE循环而不是游标。

答案 1 :(得分:1)

这可能是实现所需结果的非常详细的方法,但它适用于给定的数据集,尽管应该使用更大的数据进行测试。

我只是简单地将表格加入到parent_id匹配且child_id不同的表格中,以获得可能重叠的所有时间组合,然后执行一些{{1}在过滤和分组输出之前计算差异。

如果需要,您可以单独运行以下内容进行测试和调整:

DATEDIFF

希望有所帮助。