在T-SQL中查找最大时间重叠
我正在尝试在SQLServer2008R2上执行此操作 我有一个包含4列的表:在T-SQL中查找最大时间重叠,sql,sql-server,tsql,sql-server-2008-r2,Sql,Sql Server,Tsql,Sql Server 2008 R2,我正在尝试在SQLServer2008R2上执行此操作 我有一个包含4列的表: parent_id INT child_id INT start_time TIME end_time TIME 您应该将子进程看作是为父程序运行的子进程。所有这些子进程每天运行一次,每个子进程在其给定的时间范围内运行。我想根据每个父进程的子进程的时间,找出每个父进程的时间间隔的最大重叠,也就是说,我想知道所有子进程运行的时间间隔的最长重叠。每个时间间隔每天重复的事实意味着,即使孩子的时间间隔跨越午夜(即23:00
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=13:00
,结束时间1=06:00
,开始时间2=04:00
,结束时间2=14:00
。这将产生最大重叠,即04:00-06:00=2小时start\u time=NULL
,end\u time=NULL
和valid=0
start\u time=NULL
和end\u time=NULL
。选择此选项是为了避免将一天的时间设置为00:00-24:00,这会将跨越午夜的重叠部分一分为二,即下面的父3将有两个重叠部分(23:00-24:00和00:00-004:00),而不是一个重叠部分(23:00-04:00)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个子级共享时间的重叠来找到的:
子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条的文本,以解释将全天从0:00改为0:00的决定。
编辑:用另外两个案例扩展示例。新病例的父母ID为10和11。
编辑:插入如何找到父级10重叠的说明。
编辑:澄清第3条。增加第5及6条。详细介绍了这一切。这可能是一种实现所需结果的非常详细的方法,但它适用于给定的数据集,尽管它应该使用更大的数据进行测试 我只是简单地将表连接到它本身,
parent\u id
匹配,child\u id
不同,以获得所有可能重叠的时间组合,然后在过滤和分组输出之前执行一些DATEDIFF
来计算差异
如果需要,您可以单独运行以下测试和调整:
-- setup initial table
CREATE TABLE #OverlapTable
(
[parent_id] INT ,
[child_id] INT ,
[start_time] TIME ,
[end_time] TIME
);
-- insert dummy data
INSERT INTO #OverlapTable
( [parent_id], [child_id], [start_time], [end_time] )
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 );
-- insert all combinations into a new temp table #Results with overlap calculations
SELECT *
INTO #Results
FROM ( SELECT t1.parent_id ,
t1.start_time ,
t1.end_time ,
t2.start_time AS t2_start_time ,
t2.end_time AS t2_end_time ,
CASE WHEN t1.start_time IS NULL
AND t1.end_time IS NULL THEN 0
WHEN t1.start_time BETWEEN t2.start_time
AND t2.end_time
THEN DATEDIFF(HOUR, t1.start_time, t2.end_time)
WHEN t1.end_time BETWEEN t2.start_time AND t2.end_time
THEN DATEDIFF(HOUR, t2.start_time, t1.end_time)
ELSE NULL
END AS Overlap
FROM #OverlapTable t1
INNER JOIN #OverlapTable t2 ON t2.parent_id = t1.parent_id
AND t2.child_id != t1.child_id
) t
-- SELECT * FROM #Results -- this shows intermediate results
-- filter and group results with the largest overlaps and handle other cases
SELECT DISTINCT
r.parent_id ,
CASE WHEN r.Overlap IS NULL THEN NULL
ELSE CASE WHEN r.start_time IS NULL THEN r.t2_start_time
ELSE r.start_time
END
END start_time ,
CASE WHEN r.Overlap IS NULL THEN NULL
ELSE CASE WHEN r.end_time IS NULL THEN r.t2_end_time
ELSE r.end_time
END
END end_time ,
CASE WHEN r.Overlap IS NULL THEN 0
ELSE 1
END Valid
FROM #Results r
WHERE EXISTS ( SELECT parent_id ,
MAX(Overlap)
FROM #Results
WHERE r.parent_id = parent_id
GROUP BY parent_id
HAVING MAX(Overlap) = r.Overlap
OR ( MAX(Overlap) IS NULL
AND r.Overlap IS NULL
) )
DROP TABLE #Results
DROP TABLE #OverlapTable
希望能有所帮助。基于您的问题,我认为您的输出应该是:
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
DECLARE@Times表
(
父id INT
,child_id INT
,开始时间
,结束时间
);
插入@Times
价值观
(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,空,空)
,(3, 2, '23:00', '04:00')
,(4,1,空,空)
,(4,2,空,空)
,(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');
声明@Parents表
(
父\u id INT主键
,ChildCount INT
)
插入到@Parents中
挑选
家长id
,计数(不同的子项id)为ChildCount
从…起
@时代
分组
家长id
声明@StartTime DATETIME2='00:00'
声明@MinutesInTwoDays INT=2880
声明@Minutes表(thisminutedatetime2主键);
具有
微小的
(
挑选
1为分钟数
,@StartTime作为这一分钟
联合所有
挑选
下一分钟
,下一分钟
从小到大
交叉应用(值(分钟数+1,日期添加(分钟,1,本分钟)))下一个日期(下一个分钟数,下一个分钟)
哪里
下一分钟数次。结束时间然后是1
其他0
结束
,强制转换(ISNULL(Times.end_time,'00:00')作为日期时间2)
)作为结束时间
从…起
@时代
联合所有
挑选
Times.parent\u id
,Times.child\u id
,DATEADD(第1天,将Times.start_time转换为datetime2))转换为start_time
,DATEADD(第1天,强制转换(Times.end_time为datetime2))为end_time
从…起
@时代
哪里
开始时间<结束时间
)
--分秒必争