MySQL中重叠日期时间范围的总和

时间:2009-07-18 18:59:08

标签: datetime mysql

我有一个事件表,每个事件在MySQL表中都有一个StartTime和EndTime(作为DateTime类型)。

我正在尝试输出重叠时间总和以及重叠的事件数。

在MySQL中执行此查询的最有效/最简单的方法是什么?

CREATE TABLE IF NOT EXISTS `events` (
  `EventID` int(10) unsigned NOT NULL auto_increment,
  `StartTime` datetime NOT NULL,
  `EndTime` datetime default NULL,
  PRIMARY KEY  (`EventID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=37 ;


INSERT INTO `events` (`EventID`, `StartTime`, `EndTime`) VALUES
(10001, '2009-02-09 03:00:00', '2009-02-09 10:00:00'),
(10002, '2009-02-09 05:00:00', '2009-02-09 09:00:00'),
(10003, '2009-02-09 07:00:00', '2009-02-09 09:00:00');


# if the query was run using the data above,
# the table below would be the desired output

# Number of Overlapped Events | Total Amount of Time those events overlapped.
1, 03:00:00
2, 02:00:00
3, 02:00:00

这些结果的目的是生成一个数小时的账单。 (如果你有一个事件在运行,你可能每小时支付10美元。但是如果两个事件正在运行,你只需要每小时支付8美元,但只在你运行两个事件的那段时间内。)

3 个答案:

答案 0 :(得分:4)

试试这个:

SELECT `COUNT`, SEC_TO_TIME(SUM(Duration))
FROM (
    SELECT
        COUNT(*) AS `Count`,
        UNIX_TIMESTAMP(Times2.Time) - UNIX_TIMESTAMP(Times1.Time) AS Duration
    FROM (
        SELECT @rownum1 := @rownum1 + 1 AS rownum, `Time`
        FROM (
            SELECT DISTINCT(StartTime) AS `Time` FROM events
            UNION
            SELECT DISTINCT(EndTime) AS `Time` FROM events
        ) AS AllTimes, (SELECT @rownum1 := 0) AS Rownum
        ORDER BY `Time` DESC
    ) As Times1
    JOIN (
        SELECT @rownum2 := @rownum2 + 1 AS rownum, `Time`
        FROM (
            SELECT DISTINCT(StartTime) AS `Time` FROM events
            UNION
            SELECT DISTINCT(EndTime) AS `Time` FROM events
        ) AS AllTimes, (SELECT @rownum2 := 0) AS Rownum
        ORDER BY `Time` DESC
    ) As Times2
    ON Times1.rownum = Times2.rownum + 1
    JOIN events ON Times1.Time >= events.StartTime AND Times2.Time <= events.EndTime
    GROUP BY Times1.rownum
) Totals
GROUP BY `Count`

结果:

1, 03:00:00
2, 02:00:00
3, 02:00:00

如果这不符合您的要求,或者您想要一些解释,请告诉我。通过将重复的子查询AllTimes存储在临时表中可以加快速度,但希望它运行得足够快。

答案 1 :(得分:0)

从包含单个日期时间字段作为其主键的表开始,并使用您感兴趣的每个时间值填充该表。闰年有527040分钟(31622400秒),因此如果此表可能会变大你的活动持续了好几年。

现在加入这个表就像

一样
SELECT i.dt as instant, count(*) as events
FROM instant i JOIN event e ON i.dt BETWEEN e.start AND e.end
GROUP BY i.dt
WHERE i.dt BETWEEN ? AND ?

在instant.dt上有一个索引可能会让你放弃ORDER BY。

如果不经常添加事件,可能需要通过离线运行查询来预先计算事件,填充单独的表。

答案 2 :(得分:-1)

我建议一个具有开始时间,结束时间,#events ......的内存结构(这被简化为时间(小时),但使用unix时间可以达到第二个精度)

对于每个事件,如果没有重叠,您将按原样插入新事件,否则,找到重叠,并将事件拆分为(最多3个)可能重叠的部分,使用示例数据,从第一个事件:

事件1从凌晨3点开始,到上午10点结束:只需添加事件,因为没有重叠:

    3,10,1

事件2从凌晨5点开始,到上午9点结束:重叠,因此拆分原件,并添加新的“#events”

    3,5,1
    5,9,2
    9,10,1

事件3从上午7点开始,到上午9点结束:也是重叠,对所有时段都一样:

    3,5,1
    5,7,2
    7,9,3
    9,10,1

因此,计算每个#events的重叠小时数:

1 event= (5-3)+(10-9)=3 hours
2 events = 7-5 = 2 hours
3 events = 9-7 = 2 hours

如果要比较许多事件,将其作为后台进程运行是有意义的。