我如何从 table 中 select 每小时计数,包括缺失的小时数?
How can I select hourly counts from a table, including missing hours?
我希望按小时收集计数。但并不是每个小时都在我的 table.
中表示
为确保数据始终包含空闲时间,我构建了一个小时 table,其日期时间为 2000 年至 2037 年。我想我可以 LEFT JOIN
数据 table 到这个 table 来跟踪丢失的时间。但我需要帮助。
Table:date_hour
:
`hour`
2000-01-01 00:00:00
2000-01-01 01:00:00
...
2036-12-31 23:00:00
Table 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
去掉分秒,如:
查询
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 中没有数据)
如果您 LEFT JOIN
函数的结果,例如 DATE_FORMAT
或任何其他函数,它会产生正确的结果,但它可能比它本来可以的要慢得多。如果@amdixon 的答案中显示的简单方法的性能是 suitable,那么就使用它。
但是,您可以采取一些措施来加快速度。一旦您的 table 增长到 3000 万行(30 天,每天 100 万行),您可能需要考虑它们。
不言而喻,table date_hour
必须在 hour
列上有一个索引(实际上是主键)。当您使用如下搜索条件时,这将有助于快速找到特定日期的几行:
WHERE
date_hour.hour >= '2015-05-01 00:00:00'
AND date_hour.hour < '2015-05-02 00:00:00'
另一件需要牢记的重要事情 - 如果您在某一天有 100 万行并且您需要计算当天的计数,那么服务器必须至少读取这 100 万行。你无法避免这一点。读取1M行不会很快,但是如果整个table是30M行,那么只读取1M行显然比整个table.
要好。
因此,服务器应该能够有效地找到特定日期的行(阅读 - 应该有一个索引)。
任何在加入时从 log_date
列中即时删除分钟和秒的查询将无法使用索引,因此服务器必须扫描整个 table my_data
.
选项 1
在 my_data
.log_date
上添加索引。向 WHERE
子句添加显式过滤器。它不会改变结果,但希望能给服务器一个很好的提示,让服务器使用 my_data
.log_date
上的索引来查找必要的行并避免完全扫描。当您使用 DATE_FORMAT
将 datetime
转换为字符串时,也许 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_hour
和 my_data
上的索引来查找必要的行,它仍然必须根据函数的结果进行连接,并且对于 1M 行,这可能会很困难。很可能它必须将函数的 1M 结果存储到临时 table 中,对其进行排序然后加入。这种排序通常很昂贵,特别是如果它们不是在内存中完成的(1M 行很可能在磁盘上完成)。
选项 2
为了进一步优化并避免动态操纵 datetime
,我会考虑向 my_data
table 添加一个持久列 log_hour
,这将是与主列 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
;
我希望按小时收集计数。但并不是每个小时都在我的 table.
中表示为确保数据始终包含空闲时间,我构建了一个小时 table,其日期时间为 2000 年至 2037 年。我想我可以 LEFT JOIN
数据 table 到这个 table 来跟踪丢失的时间。但我需要帮助。
Table:date_hour
:
`hour`
2000-01-01 00:00:00
2000-01-01 01:00:00
...
2036-12-31 23:00:00
Table 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
去掉分秒,如:
查询
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 中没有数据)
如果您 LEFT JOIN
函数的结果,例如 DATE_FORMAT
或任何其他函数,它会产生正确的结果,但它可能比它本来可以的要慢得多。如果@amdixon 的答案中显示的简单方法的性能是 suitable,那么就使用它。
但是,您可以采取一些措施来加快速度。一旦您的 table 增长到 3000 万行(30 天,每天 100 万行),您可能需要考虑它们。
不言而喻,table date_hour
必须在 hour
列上有一个索引(实际上是主键)。当您使用如下搜索条件时,这将有助于快速找到特定日期的几行:
WHERE
date_hour.hour >= '2015-05-01 00:00:00'
AND date_hour.hour < '2015-05-02 00:00:00'
另一件需要牢记的重要事情 - 如果您在某一天有 100 万行并且您需要计算当天的计数,那么服务器必须至少读取这 100 万行。你无法避免这一点。读取1M行不会很快,但是如果整个table是30M行,那么只读取1M行显然比整个table.
要好。因此,服务器应该能够有效地找到特定日期的行(阅读 - 应该有一个索引)。
任何在加入时从 log_date
列中即时删除分钟和秒的查询将无法使用索引,因此服务器必须扫描整个 table my_data
.
选项 1
在 my_data
.log_date
上添加索引。向 WHERE
子句添加显式过滤器。它不会改变结果,但希望能给服务器一个很好的提示,让服务器使用 my_data
.log_date
上的索引来查找必要的行并避免完全扫描。当您使用 DATE_FORMAT
将 datetime
转换为字符串时,也许 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_hour
和 my_data
上的索引来查找必要的行,它仍然必须根据函数的结果进行连接,并且对于 1M 行,这可能会很困难。很可能它必须将函数的 1M 结果存储到临时 table 中,对其进行排序然后加入。这种排序通常很昂贵,特别是如果它们不是在内存中完成的(1M 行很可能在磁盘上完成)。
选项 2
为了进一步优化并避免动态操纵 datetime
,我会考虑向 my_data
table 添加一个持久列 log_hour
,这将是与主列 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
;