我发现这个问题很有趣,所以花了一点时间来制定解决方案。我想出的是按名称和开始时间对行进行排序,然后使用 MySQL 变量来说明重叠范围。我首先对表格进行排序,并用从一行到下一行的名称和时间来补充它
SELECT [expounded below]
FROM (SELECT * FROM tbl ORDER BY Name, START, END) AS u,
(SELECT @x := 0, @gap := 0, @same_name:='',
@beg := (SELECT MIN(START) FROM tbl),
@end := (SELECT MAX(END) FROM tbl)) AS t
这会将时间范围的名称和外部边界添加到表格的每一行,并对表格进行排序,以便
名字按开始时间排列在一起。对于每一行,我们现在将有@same_name、@beg 和@end 将值从一行向前传递到下一行,@x 和@gap 将累积小时数。
现在我们必须对可能发生的重叠进行一些推理。对于任意两个区间,它们要么不相交,要么有交集:
Non-overlapping: beg--------end START-------END
Overlapping: beg-----------end beg---------end
START--------------END START-----------END
Subset: beg---------------------------------end
START-----END
一旦行相邻,我们可以通过比较它们的起点和终点来确定两个范围是否重叠。它们重叠
如果一个的开始在另一个的结束之前,反之亦然:
IF( @end >= START && @beg <= END,
如果它们确实重叠,则总间隔是两个间隔的外边缘之间的差:
TIMESTAMPDIFF(HOUR, LEAST(@beg, START), GREATEST(@end, END))
如果它们不重叠,那么我们可以将新间隔添加到前一个间隔。
我们还需要知道间隔之间的差距,即从第一个结束到第二个开始的差异。这对于计算超过两个间隔的情况下的小时数是必要的,其中只有一些重叠。
1-----------2 3----------4
3--------------------5
把这些放在一起可以得到每行的计算,其中每一行计算小时与一个小时的并集
它上面。对于每个变量,如果名称发生变化,我们必须重新设置它:
SELECT Name, START, END,
@x := IF(@same_name = Name,
IF( @end >= START && @beg <= END, -- does it overlap?
TIMESTAMPDIFF(HOUR, LEAST(@beg, START), GREATEST(@end, END)),
@x + TIMESTAMPDIFF(HOUR, START, END) ),
TIMESTAMPDIFF(HOUR,START,END) ) AS hr,
@gap := IF(@same_name = Name,
IF(@end >= START && @beg <= END, -- does it overlap?
@gap,
@gap + TIMESTAMPDIFF(HOUR, @end, START)),
0) AS gap,
@beg := IF(@same_name = Name,
CAST(LEAST(@beg, START) AS DATETIME), -- expand interval
START) AS beg, -- reset interval
@end := IF(@same_name = Name,
CAST(GREATEST(@end, END) AS DATETIME),
END) AS finish,
@same_name := Name AS sameName
FROM
(SELECT * FROM xt ORDER BY Name, START, END) AS u,
(SELECT @x := 0, @gap := 0, @same_name:='', @beg := (SELECT MIN(START) FROM xt), @end := (SELECT MAX(END) FROM xt)) AS t
这仍然为我们提供了与原始表中一样多的行。每个名称的小时数和间隔将累积,因此我们必须选择最高值并按名称分组:
SELECT Name, MAX(hr) - MAX(gap) AS HOURS
FROM ( [insert above query here] ) AS intermediateCalculcation
GROUP BY Name;
编辑
当然,在按下回车后不久,我突然想到(a)对于完全没有重叠间隔的名称存在错误; (b) 所有@x 真正做的是为每个名称建立从 MIN(START) 到 MAX(END) 的间隔,这可以通过更简单的查询和连接来完成。嗯,为读者锻炼? :-)