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

标签 sql sql-server t-sql sql-server-2008-r2

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

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

parent_id INT
child_id INT
start_time TIME
end_time TIME

您应该将子进程视为为父程序运行的子进程。所有这些子流程每天运行一次,并且每个子流程在给定的时间范围内运行。我想根据其子进程的时间找到每个父进程的最大时间间隔重叠,即我想知道所有子进程运行的最长可能重叠。每个时间跨度每天都会重复,这意味着即使 child 的时间间隔跨越午夜(即 23:00-10:00),它也可以与只在早上运行的 child (即 07:00-09)重叠: 00),因为即使它们在“第一天”不重叠,它们也会在随后的所有日子重叠。

输出应如下所示:

parent_id INT
start_time TIME
end_time TIME
valid BIT

哪里valid = 1如果发现重叠并且 valid = 0如果没有发现重叠。

一些重要的信息:

  1. 时间间隔可以跨越午夜,即 start_time = 23:00end_time = 03:00 ,时间间隔为 4 小时。
  2. 两个时间间隔可能在两个不同的地方重叠,即 start_time1 = 13:00 , end_time1 = 06:00 , start_time2 = 04:00 , end_time2 = 14:00 。这将产生最大重叠时间 04:00 - 06:00 = 2 小时。
  3. 给定父级的子级可能没有常见的重叠,在这种情况下,该父级的输出将为 start_time = NULL , end_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. 一个 child 的时间跨度不得超过 24 小时。

举个例子:

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

父级的重叠是由其所有子级共享的时间间隔。因此,父级 10 的重叠是通过查找所有 3 个子级共享时间的重叠来找到的: child 1 (06:11-14:00) 和 child 2 (06:00-09:00) 在 06:11 至 09:00 之间重叠。然后,将此重叠时间间隔应用于子级 3 (05:00-08:44),这会产生 06:11 到 08:44 的重叠,因为此间隔是所有 3 个子级共享共同时间的唯一间隔。

我希望这是有道理的。

我可以使用光标来完成此操作,但我真的更愿意避免使用光标。我一直在绞尽脑汁地思考如何在没有光标的情况下做到这一点,但我没有成功。有没有办法不用光标来做到这一点?

编辑:扩展了第 4 条的文本,以解释全天为 NULL 到 NULL(而不是 00:00 到 00:00)的决定。 编辑:用另外两个案例扩展了示例。新病例的父 ID 为 10 和 11。 编辑:插入了如何找到父级 10 重叠的解释。 编辑:澄清了第 3 条。添加了第 5 条和第 6 条。详细说明了这一切的含义。

最佳答案

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

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 循环而不是游标。

关于sql - 在 T-SQL 中查找最大时间重叠,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29234284/

相关文章:

php - 在列而不是行中显示 SQLITE 输出

sql - 仅当匹配条件时才选择列值

java - Hibernate 将两个表映射到一个类

mysql - 将时间戳转换为正确的格式

sql-server - 对于大型表,SET SYSTEM_VERSIONING = ON 期间超时

sql - 查询所有表中所有包含值的字段

sql - 按来源统计每次购买前 30 天的访问量

sql - 如何有条件地为 SQL CREATE TABLE 语句构造表名?

javascript - 如何使用 Angular JS 将数据发布到 SQL Server

t-sql - Azure 流分析将多个输入分组到一个输出