TSQL:将连续时隙分组在一起

TSQL:将连续时隙分组在一起,tsql,Tsql,我必须将连续时间段分组在一起: 例如: DECLARE @TEST as Table (ID int, tFrom datetime, tUntil dateTime) insert into @TEST Values (1,'2019-1-1 12:00', '2019-1-1 13:00') insert into @TEST Values (1,'2019-1-1 13:00', '2019-1-1 14:00') insert into @TEST Values (1,'2019-1-1

我必须将连续时间段分组在一起:

例如:

DECLARE @TEST as Table (ID int, tFrom datetime, tUntil dateTime)
insert into @TEST Values (1,'2019-1-1 12:00', '2019-1-1 13:00')
insert into @TEST Values (1,'2019-1-1 13:00', '2019-1-1 14:00')
insert into @TEST Values (1,'2019-1-1 14:00', '2019-1-1 16:00')
insert into @TEST Values (1,'2019-1-1 18:00', '2019-1-1 19:00')
insert into @TEST Values (1,'2019-1-1 19:00', '2019-1-1 20:00')
insert into @TEST Values (1,'2019-1-1 20:00', '2019-1-1 21:00')
insert into @TEST Values (1,'2019-1-1 22:00', '2019-1-1 23:00')
insert into @TEST Values (2,'2019-1-1 12:00', '2019-1-1 13:00')
insert into @TEST Values (2,'2019-1-1 13:00', '2019-1-1 14:00')
insert into @TEST Values (2,'2019-1-1 14:00', '2019-1-1 16:00')
insert into @TEST Values (2,'2019-1-1 18:00', '2019-1-1 19:00')
insert into @TEST Values (2,'2019-1-1 19:00', '2019-1-1 20:00')
insert into @TEST Values (2,'2019-1-1 20:00', '2019-1-1 21:00')
insert into @TEST Values (2,'2019-1-1 22:00', '2019-1-1 23:00')
预期结果:

1; 2019-1-1 12:00; 2019-1-1 16:00
1; 2019-1-1 18:00; 2019-1-1 21:00
1; 2019-1-1 22:00; 2019-1-1 23:00
2; 2019-1-1 12:00; 2019-1-1 16:00
2; 2019-1-1 18:00; 2019-1-1 21:00
2; 2019-1-1 22:00; 2019-1-1 23:00

这是一个分类缺口和孤岛问题。 这里的关键是如何识别群体

如果
t从
t直到
之间的差异始终正好是一个小时,则可以忽略该时间段,仅根据不同记录的
t从
之间的差异进行工作。
使用通用表表达式标识组,然后从中选择
min(tFrom)
max(tUntil)
,按id和组分组

您要做的是计算
t从
到某个固定日期之间的小时差,然后从
t从
排序的
行数
中减去该值(在本例中,该值由
id
进行分区)

这意味着来自的
t的连续值将获得相同的组密钥(在本例中,连续在这里表示按小时):

如果
t从
t直到
之间的差异不固定,那么识别组将更加麻烦。
我提出了一个涉及三个常用表表达式的解决方案-第一个是从
中获取当前行的
tunitil
和下一行的
tf之间的日期差,然后根据前一行的差分计算一个组除法器,然后根据除法器的和计算组id:

WITH CTE1 AS
(
    SELECT  ID, 
            tFrom, 
            tUntil,
            DATEDIFF(HOUR, tUntil, LEAD(tFrom) OVER(PARTITION BY id  ORDER BY tFrom)) As DiffNext
    FROM @Test
), CTE2 AS
(
    SELECT  ID, 
            tFrom, 
            tUntil,
            ISNULL(SIGN(LAG(DiffNext) OVER(PARTITION BY id  ORDER BY tFrom)), 1) AS GroupDivider
    FROM CTE1
), CTE3 AS
(
    SELECT  ID, 
            tFrom, 
            tUntil,
            SUM(GroupDivider) OVER(PARTITION BY id  ORDER BY tFrom) As GroupId
    FROM CTE2
)

SELECT  ID, 
        MIN(tFrom) As tFrom, 
        MAX(tUntil) As tUntil
FROM CTE3
GROUP BY ID, GroupId
ORDER BY ID, tFrom

这是一个分类缺口和孤岛问题。 这里的关键是如何识别群体

如果
t从
t直到
之间的差异始终正好是一个小时,则可以忽略该时间段,仅根据不同记录的
t从
之间的差异进行工作。
使用通用表表达式标识组,然后从中选择
min(tFrom)
max(tUntil)
,按id和组分组

您要做的是计算
t从
到某个固定日期之间的小时差,然后从
t从
排序的
行数
中减去该值(在本例中,该值由
id
进行分区)

这意味着来自
t的连续值将获得相同的组密钥(在本例中,连续在这里表示按小时):

如果
t从
t直到
之间的差异不固定,那么识别组将更加麻烦。
我提出了一个涉及三个常用表表达式的解决方案-第一个是从
中获取当前行的
tunitil
和下一行的
tf之间的日期差,然后根据前一行的差分计算一个组除法器,然后根据除法器的和计算组id:

WITH CTE1 AS
(
    SELECT  ID, 
            tFrom, 
            tUntil,
            DATEDIFF(HOUR, tUntil, LEAD(tFrom) OVER(PARTITION BY id  ORDER BY tFrom)) As DiffNext
    FROM @Test
), CTE2 AS
(
    SELECT  ID, 
            tFrom, 
            tUntil,
            ISNULL(SIGN(LAG(DiffNext) OVER(PARTITION BY id  ORDER BY tFrom)), 1) AS GroupDivider
    FROM CTE1
), CTE3 AS
(
    SELECT  ID, 
            tFrom, 
            tUntil,
            SUM(GroupDivider) OVER(PARTITION BY id  ORDER BY tFrom) As GroupId
    FROM CTE2
)

SELECT  ID, 
        MIN(tFrom) As tFrom, 
        MAX(tUntil) As tUntil
FROM CTE3
GROUP BY ID, GroupId
ORDER BY ID, tFrom
你好

为了有一个覆盖时间范围内重叠的灵活解决方案,我们可以使用几种解决方案。“间隙和孤岛”方法不是最好的(从性能角度来看),但它会起作用,还有更糟糕的选择(如使用循环/光标)。由于“缺口和孤岛”是评论中提到的短语,也是评论中讨论的解决方案中提到的短语,因此我将首先简要说明此解决方案

使用“间隙和孤岛”方法的解决方案基于两个步骤(一个查询使用CTE)。首先,将范围划分为“时间点”。接下来使用“数字”表或更好的“时间”表,您可以通过查找点之间的间隙来获得最终结果集,这是典型的“间隙和孤岛”问题

我强烈建议您,并且从头到尾都要遵循它!这种方法有局限性和缺点,您必须理解。此外,这篇文章还介绍了“思维方式”,以及我们如何一步一步地解决这样的问题

在本文中,我从整数范围的最简单情况开始,例如2-4、6-8、8-10、13-14,它们应该被分为2-4、6-10、13-14

接下来,我将解释与范围之间的空间分辨率相关的问题,并为十进制数范围提供一个解决方案,涵盖了该问题

最后,使用我详细介绍的整数解决方案,我提出了“将连续时隙分组在一起”的解决方案,这是论坛的原始问题

注意这里介绍的解决方案可能就是我推荐在生产中使用的解决方案。在我的下一篇文章中,我使用我的个人技巧发布了一种完全不同的方法,它可以显著提高性能

简而言之,为了便于讨论,我将创建一个时间表(如果您真的愿意,可以直接使用数字表)。请注意,我使用数字表创建了时间表

DROP TABLE IF EXISTS Times
GO
SELECT DT = DATEADD(MINUTE, N*10, '2010-01-01')
    INTO Times
FROM Numbers
GO
CREATE CLUSTERED INDEX IX_DT ON Times(DT)
GO
SELECT TOP 1000 DT from Times
GO
使用这个表我们可以解决这个问题

;With MyCTE01 as (
    SELECT DISTINCT ID, DT
    FROM TEST t
    INNER JOIN Times dt ON DT between tFrom and tUntil
)
,MyCTE02 as(
    SELECT ID, DT,
        MyGroup =
            DATEDIFF(MINUTE,
                DATEADD(MINUTE, 10 * ROW_NUMBER()OVER(PARTITION BY ID ORDER BY ID,DT),0),
                DT
            )
    from MyCTE01
    --order by ID,DT
)
SELECT ID, MIN(DT) tFrom, MAX(DT) tUntil
FROM MyCTE02
GROUP BY ID, MyGroup
ORDER BY ID, tFrom
GO
注意在选择适合您生产的解决方案之前,我会高度关注(第2部分)

我希望这篇文章涵盖了讨论内容,并对大家有所帮助祝大家好

为了有一个覆盖时间范围内重叠的灵活解决方案,我们可以使用几种解决方案。“间隙和孤岛”方法不是最好的(从性能角度来看),但它会起作用,还有更糟糕的选择(如使用循环/光标)。由于“缺口和孤岛”是评论中提到的短语,也是评论中讨论的解决方案中提到的短语,因此我将首先简要说明此解决方案

使用“间隙和孤岛”方法的解决方案基于两个步骤(一个查询使用CTE)。首先,将范围划分为“时间点”。接下来使用“数字”表或更好的“时间”表,您可以通过查找点之间的间隙来获得最终结果集,这是经典的“间隙和孤岛”专业版