我必须将连续的时隙分组在一起:
示例:
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
答案 0 :(得分:0)
这是分类差距和岛屿问题。 这里的关键是如何识别组。
如果tFrom
和tUntil
之间的差异始终恰好是一个小时,则可以忽略tUntil并仅根据不同记录的tFrom
之间的差异进行工作。
使用公用表表达式来标识组,然后从中选择min(tFrom)
和max(tUntil)
(按ID和组分组)。
您要做的是计算tFrom
与某个固定日期之间的时差,然后从row_number
排序的tFrom
中减去该值(并按id
进行分区在这种情况下)。
这意味着tFrom
的连续值将获得相同的组密钥(在这种情况下,此处连续表示小时):
WITH CTE AS
(
SELECT ID,
tFrom,
tUntil,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY tFrom) -
DATEDIFF(HOUR, '2019-01-01', tFrom) As grp
FROM @Test
)
SELECT ID,
MIN(tFrom) As tFrom,
MAX(tUntil) As tUntil
FROM CTE
GROUP BY ID, grp
ORDER BY Id, tFrom
如果tFrom
和tUntill
之间的差异不固定,那么识别组将变得更加麻烦。
我想出了一个涉及三个常用表表达式的解决方案-第一个是获取当前行的tUntill
与下一行的tFrom
之间的datediff,然后根据上一行的差计算组分隔符,然后然后根据除法器的总和计算组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
答案 1 :(得分:0)
美好的一天,
为了有一个灵活的解决方案来覆盖时间范围内的重叠,我们可以使用几种解决方案。从性能的角度来看,“ 间隙和孤岛”方法不是最好的方法,但是它会起作用,并且还有更差的选择(例如使用循环/光标)。由于“空白与离岛”是评论中以及在评论中讨论的解决方案中提到的短语,因此我将首先简短地展示此解决方案。
使用“空白和离岛”方法的解决方案基于两个步骤(一个查询使用CTE)。首先,将范围划分为“时间点”。接下来,使用“数字”表或在这种情况下更好的是“时间”表,您可以通过找到两点之间的间隙来获得最终结果SET,这是经典的“间隙和孤岛”问题。
我强烈建议follow the post, which I published,并从头到尾遵循它!您必须了解这种方法的局限性和缺点。此外,该帖子还介绍了“思维方式”以及我们如何逐步解决此类问题。
在本文中,我以最简单的情况为例,即整数范围,例如2-4、6-8、8-10、13-14,应将其分组为2-4, 6-10,13-14。
接下来,我将解释与范围之间的空间分辨率有关的问题,并为十进制数字的范围提供解决方案,以解决问题。
最后,使用我为INTEGERS详细介绍的解决方案,我提出了“将连续的时隙分组在一起”的解决方案,这是论坛中的原始问题。
注意!这里介绍的解决方案可能是我建议在生产中使用的解决方案。在我的下一篇文章中,我使用个人技巧发布了一种完全不同的方法,可以显着提高性能。
简而言之,为了便于讨论,我将创建一个Times表(如果需要,可以直接使用Numbers表)。注意,我使用Numbers表创建Times表。
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
注意!在选择适合您生产的解决方案之前,我高度recommend to check the second post(第2部分)。
我希望本文涵盖了讨论并且对我们有帮助