如何从表中选择每小时计数,包括缺少小时数?

时间:2015-05-09 03:31:38

标签: mysql sql date datetime hour

我希望按小时收集数量。但不是每个小时都在我的表格中。

为了确保数据始终包含空闲时间,我构建了一个小时表,其日期时间为2000-2037。我想我可以LEFT JOIN数据表到这个表来跟踪缺少的小时数。但我需要帮助。

表:date_hour

`hour`
2000-01-01 00:00:00
2000-01-01 01:00:00
...
2036-12-31 23:00:00

my_data

log_date               field1
2015-05-01 00:31:00    1000
2015-05-01 04:19:00    2000    
2015-05-01 05:19:00    1000
2015-05-01 07:19:00    100
2015-05-01 07:35:00    6000

期望的结果:

hour                   count
2015-05-01 00:00:00    1
2015-05-01 01:00:00    0
2015-05-01 02:00:00    0
2015-05-01 03:00:00    0
2015-05-01 04:00:00    1
2015-05-01 05:00:00    1
2015-05-01 06:00:00    0
2015-05-01 07:00:00    2

MySQL尝试:

SELECT
    dh.hour,
    COUNT(md.*) AS count
FROM
    date_hour dh
    LEFT JOIN my_data md ON dh.hour = ????md.log_date????
WHERE
        dh.hour >= '2015-05-01'
    AND dh.hour <  '2015-05-02'
GROUP BY
    dh.hour
ORDER BY
    dh.hour;

实现这些计数的最有效方法是什么?假设每天有100k-1MM的记录,目标是一次测量至少30天的数据。

2 个答案:

答案 0 :(得分:4)

可以使用DATE_FORMAT去除分钟和秒钟,如:

<强>查询

SELECT
    dh.hour,
    COUNT(md.*) AS count
FROM
    date_hour dh LEFT JOIN my_data md 
    ON dh.hour = DATE_FORMAT(md.log_date, "%Y-%m-%d %H:00:00")
WHERE
        dh.hour >= '2015-05-01'
    AND dh.hour <  '2015-05-02'
GROUP BY
    dh.hour
ORDER BY
    dh.hour
;

<强>输出

+------------------------+-----------+
|          hour          |   count   |
+------------------------+-----------+
| 2015-05-01 00:00:00    | 1         |
| 2015-05-01 01:00:00    | 0         |
| 2015-05-01 02:00:00    | 0         |
| 2015-05-01 03:00:00    | 0         |
| 2015-05-01 04:00:00    | 1         |
| 2015-05-01 05:00:00    | 1         |
| 2015-05-01 06:00:00    | 0         |
| 2015-05-01 07:00:00    | 2         |
| ... trailing hours ... | allzeroes |
+------------------------+-----------+

2015-05-01 08:00:00之后的所有内容都是零(my_data中没有数据)

<强> sqlfiddle

答案 1 :(得分:1)

如果LEFT JOINDATE_FORMAT这样的函数的结果或任何其他函数它会产生正确的结果,但它可能比它本来的慢。如果@amdixon在答案中显示的简单方法的表现是合适的,那么就使用它。

但是,您可以采取一些措施来加快速度。一旦表格增长到30M行(30天,每天1M行),您可能需要考虑它们。

不言而喻,表date_hour必须在hour列上有一个索引(实际上是主键)。当您使用如下搜索条件时,这将有助于快速找到特定日期的几行:

WHERE
        date_hour.hour >= '2015-05-01 00:00:00'
    AND date_hour.hour <  '2015-05-02 00:00:00'

要记住的另一个重要事项 - 如果您在某一天有1M行并且您需要计算当天的计数,那么服务器必须至少读取这些1M行。你无法避免这种情况。读取1M行不会很快,但如果整个表是30M行,那么读取整行表的行数显然要好一些。

因此,服务器应该能够有效地查找特定日期的行(读取 - 应该有一个索引)。 任何在加入时动态删除log_date列中的分钟和秒的查询都无法使用索引,因此服务器必须扫描整个表my_data

选项1

my_datalog_date上添加索引。为WHERE子句添加显式过滤器。它不会改变结果,但希望能给服务器一个很好的提示,使用my_datalog_date上的索引来查找必要的行并避免完全扫描。当使用datetimeDATE_FORMAT转换为字符串时,MySQL可能足够智能,并且它不会将date_hour.hour转换为字符串以进行比较(因此否定{{1}上存在索引的事实1}})。也许不吧。我更喜欢以下方法从date_hour.hour中删除分钟和秒,而不将其转换为字符串。

datetime

我们可以使用任何常数而不是&#39; 2015-01-01&#39;只要它没有分钟和秒钟。可以使用相同的方法将TIMESTAMPADD(HOUR, TIMESTAMPDIFF(HOUR,'2015-01-01 00:00:00',DateTimeValue), '2015-01-01 00:00:00') 截断为任何其他边界 - 分钟,日,周,月,年。

datetime

即使服务器使用SELECT date_hour.hour, COUNT(my_data.log_date) AS count FROM date_hour LEFT JOIN my_data ON date_hour.hour = TIMESTAMPADD(HOUR, TIMESTAMPDIFF(HOUR,'2015-01-01 00:00:00',my_data.log_date), '2015-01-01 00:00:00') WHERE date_hour.hour >= '2015-05-01 00:00:00' AND date_hour.hour < '2015-05-02 00:00:00' AND my_data.log_date >= '2015-05-01 00:00:00' AND my_data.log_date < '2015-05-02 00:00:00' GROUP BY date_hour.hour ORDER BY date_hour.hour ; date_hour上的索引来查找必要的行,仍然必须根据函数的结果加入,而使用1M行可能很难。很可能它必须将1M函数的结果存储到临时表中,对其进行排序然后加入。这种排序通常很昂贵,特别是如果它们不在内存中(1M行很可能在磁盘上完成)。

选项2

为了进一步优化这一点并避免动态操纵my_data,我会考虑在datetime表中添加一个持久列log_hour,该列将与主列一起填充my_data并且包含log_date值而没有分钟和秒。您可以将其视为预先计算或缓存。在此列log_date上有索引后,服务器应该能够有效地查找和连接找到的行。查询变得微不足道,它根本不使用log_hour列,它只使用log_date

log_hour