我正在尝试在 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
如果没有发现重叠。
一些重要的信息:
- 时间间隔可以跨越午夜,即
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)。 - 只有当父级的所有子级共享时间间隔时,重叠才算重叠。
- 一个 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/