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

Posted

技术标签:

【中文标题】如何从表中选择小时计数,包括缺失小时数?【英文标题】:How can I select hourly counts from a table, including missing hours? 【发布时间】:2015-05-09 03:31:37 【问题描述】:

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

为了确保数据始终包含空小时,我构建了一个小时表,其中包含 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 天的数据。

【问题讨论】:

可以在DATE_FORMAT(my_data.log_date, "%Y-%m-%d %H:00:00")离开加入 ...并且在 hors 表中只保留 24 个记录 您的 SQL 显示正确。在 dh.hour 和 md.log_date 上创建索引并使用 where dh.hour &gt;= '2015-05-01 00:00:00' and dh.hour &lt; '2015-05-02 00:00:00' 以保持表示日期/时间的一致性。 @amdixon,干得好。这工作得很好。谢谢。随意添加作为答案。 2000-2037? 0-23 不够吗?虽然,就我个人而言,我更喜欢在应用程序级代码中处理丢失结果的逻辑。 【参考方案1】:

可以使用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

【讨论】:

连接应该是左外连接,所以返回 my_data 表中有 0 条记录的小时数?【参考方案2】:

如果您在 DATE_FORMAT 之类的函数或任何其他函数的结果上使用 LEFT JOIN,它将产生正确的结果,但它可能会比原本的速度慢得多。如果@amdixon 的答案中显示的简单方法的性能合适,那么就使用它。

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

不用说,表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 行,那么显然只读取 1M 行比读取整个表要好。

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

选项 1

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

TIMESTAMPADD(HOUR,
    TIMESTAMPDIFF(HOUR,'2015-01-01 00:00:00',DateTimeValue),
    '2015-01-01 00:00:00')

我们可以使用任何常量来代替“2015-01-01”,只要它没有分钟和秒。可以使用相同的方法将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_hourmy_data 上的索引来查找必要的行,它仍然必须根据函数的结果进行连接,并且有1M 行可能会很困难。很可能它必须将函数的 1M 结果存储到临时表中,对其进行排序然后加入。这些类型通常很昂贵,特别是如果它们不在内存中完成(1M 行很可能在磁盘上完成)。

选项 2

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

SELECT
    date_hour.hour,
    COUNT(my_data.log_hour) AS count
FROM
    date_hour
    LEFT JOIN my_data ON date_hour.hour = my_data.log_hour
WHERE
    date_hour.hour   >= '2015-05-01 00:00:00' AND
    date_hour.hour   <  '2015-05-02 00:00:00' AND
    my_data.log_hour >= '2015-05-01 00:00:00' AND
    my_data.log_hour <  '2015-05-02 00:00:00'
GROUP BY
    date_hour.hour
ORDER BY
    date_hour.hour
;

【讨论】:

非常聪明。也会对此进行调查。谢谢。

以上是关于如何从表中选择小时计数,包括缺失小时数?的主要内容,如果未能解决你的问题,请参考以下文章

如何从表中选择所有列并计数?

如何在 hbase 表中获取计数记录?查询记录的最快方法是啥?

如何使用 group by(基于一列)从表中选择多列,在 hive 查询中具有和计数

如何使用表中的“时间戳”列选择一个小时内“值”列的平均值

SQL 命令:从表中获取最小日期和小时

从表中检索特定 24 小时时间范围内的记录