【问题标题】:MYSQL - Sum Interval DatesMYSQL - 和间隔日期
【发布时间】:2014-12-17 09:39:25
【问题描述】:

我遇到了以下问题:

我想总结每个名字的小时数,给出STARTEND 活动之间的总间隔, 如果我可以从每条记录中减去开头的结尾会很简单,例如,玛丽,从 13 岁开始,一直到 15 岁,并在 14 岁和 16 岁时开始另一项活动,我希望结果是 3(她使用了 3他们的时间来执行这两项活动)

例如:

Name    |    START               |    END                 |
-----------------------------------------------------------
KATE    | 2014-01-01 13:00:00    | 2014-01-01 14:00:00    |
MARY    | 2014-01-01 13:00:00    | 2014-01-01 15:00:00    |
TOM     | 2014-01-01 13:00:00    | 2014-01-01 16:00:00    |
KATE    | 2014-01-01 12:00:00    | 2014-01-02 04:00:00    |
MARY    | 2014-01-01 14:00:00    | 2014-01-01 16:00:00    |
TOM     | 2014-01-01 12:00:00    | 2014-01-01 18:00:00    |
TOM     | 2014-01-01 22:00:00    | 2014-01-02 02:00:00    |

结果:

KATE    15 hours
MARY    3 hours
TOM      9 hours

【问题讨论】:

  • 使用 TIMESTAMPDIFF 函数可以轻松计算每行的小时数差异:TIMESTAMPDIFF(HOUR, START, END)。在不同行的重叠区间中找到并集是一个困难得多的问题。

标签: mysql datetime


【解决方案1】:

我发现这个问题很有趣,所以花了一点时间来制定解决方案。我想出的是按名称和开始时间对行进行排序,然后使用 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) 的间隔,这可以通过更简单的查询和连接来完成。嗯,为读者锻炼? :-)

【讨论】:

  • 这有点矫枉过正,因为解决方案应该是一个简单的 group by 语句:)
  • 简单的 GROUP BY 并不能解决问题,因为它没有考虑重叠。例如,玛丽的结果应该是 3,因为她有两行重叠,从 13 到 15 和 14 到 16。简单的 GROUP BY 不考虑重叠,答案为 4。
【解决方案2】:

您是否尝试过 group by 然后聚合函数?

SELECT Name, SUM(UNIX_TIMESTAMP(End) - UNIX_TIMESTAMP(Start)) FROM myTable
GROUP BY Name 

这将返回您所拥有的时间间隔的累计秒数。然后,您可以将显示的秒数更改为小时。

此外,我强烈建议按主键或其他内容而不是字符串名称进行分组,但我知道这可能只是为了简化问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 2012-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多