我试图在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}}。
一些重要的信息:
start_time = 23:00
和end_time = 03:00
,时间间隔为4小时。start_time1 = 13:00
,end_time1 = 06:00
,start_time2 = 04:00
,end_time2 = 14:00
。这将是04:00 - 06:00 = 2小时的最大重叠。start_time = NULL
,end_time = NULL
和valid = 0
。start_time = NULL
和end_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
父级的重叠是其所有子级共享的时间间隔。因此,通过找到所有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条。详细了解这是什么。
答案 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
希望有所帮助。