包括使用GROUP BY时丢失(零计数)行

时间:2014-06-19 11:07:16

标签: mysql count group-by aggregate-functions

我有一个接收短信的应用程序。我想要做的是使用mysql进行统计,这将在一小时内计算消息。例如,早上7点我收到了10条短信,早上8点我收到20条等等。我的表有这个列ID,smsText,smsDate ......(其他都不重要)。当我运行这个脚本时:

SELECT HOUR(smsDate), COUNT(ID) FROM SMS_MESSAGES GROUP BY HOUR(smsDate)

它显示我每小时收到多少消息。问题是当我没有收到任何消息,例如在下午5点,这个语句不会返回第17行计数0,我有这样的结果:

Hour Count
...
15 10
16 5
18 2
...

,我想要的是这个

Hour Count
...
15 10
16 5
17 0
18 2
...

我在网上搜索了一个解决方案,但是我不知道如何在我的网站上实现这个解决方案。希望有人可以帮助我。

3 个答案:

答案 0 :(得分:3)

您可以创建一个包含所有小时数的表格并加入表格:

CREATE TABLE IF NOT EXISTS `hours` (
  `hour` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `hours` (`hour`) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (13), (14), (15), (16), (17), (18), (19), (20), (21), (22), (23);

SELECT hours.hour, count( SMS_MESSAGES.ID ) 
FROM hours
LEFT JOIN SMS_MESSAGES ON ( hours.hour = HOUR( SMS_MESSAGES.smsDate ) ) 
GROUP BY 1 

答案 1 :(得分:2)

由于hellocode回答创建一个包含小时值的新表是一个很好的方法,这是通过使用union实现这一目的的另一种方法

select t.`hour`,count(s.ID) from (
select 0 as `hour`
union
select 1 as `hour`
union
select 2 as `hour`
union
.
.
.
select 23 as `hour`
) t
left join SMS_MESSAGES s on(t.`hour` = hour(s.smsDate))
group by t.`hour`

答案 2 :(得分:1)

观察:HOUR()只是从时间戳中提取小时。您可能需要查询中的日期和小时。这个答案提供了日期和时间。

您需要一种方法来获取包含适当范围内所有每小时时间戳的虚拟表。然后,您需要将该表连接到聚合查询。

首先要做的事情:这是一个查询,它将获得范围内的时间戳。

SELECT mintime + INTERVAL seq.seq HOUR AS msghour
  FROM (
        SELECT MIN(DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR) AS mintime,
               MAX(DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR) AS maxtime
          FROM SMS_MESSAGES
       ) AS minmax
  JOIN seq_0_to_999999 AS seq ON seq.seq < TIMESTAMPDIFF(HOUR,mintime,maxtime)

这里发生了什么?三件事。

首先:DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR将任意时间戳转换为小时顶部的时间戳。这使我们可以获取表格中的第一个和最后一个小时时间戳。

其次,我们有一个子查询,它确定我们关心报告的第一个和最后一个小时(最小和最大smsDate)。

其次,我们有一个名为seq_0_to_999999的表。它包含一系列基数:从零开始的整数。更多关于此的内容。

将这两个表连接在一起,然后使用表达式

mintime + INTERVAL seq.seq HOUR AS msghour

我们可以获取一个连续运行每小时时间戳的表。

然后我们将其加入您的查询。在这里,它开始看起来更加复杂。我们正在大纲中这样做:

 SELECT DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR, COUNT(ID)
   FROM SMS_MESSAGES 
   JOIN ( /*the query above wit the sequence of timestamps*/) AS sq 
     ON DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR = msghour
  GROUP BY DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR
  ORDER BY DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR

总而言之,它看起来像这样:

 SELECT DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR, COUNT(ID)
   FROM SMS_MESSAGES 
   JOIN ( 
        SELECT mintime + INTERVAL seq.seq HOUR AS msghour
          FROM (
                SELECT MIN(DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR) AS mintime,
                       MAX(DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR) AS maxtime
                  FROM SMS_MESSAGES
               ) AS minmax
          JOIN seq_0_to_999999 AS seq ON seq.seq < TIMESTAMPDIFF(HOUR,mintime,maxtime)
       ) AS sq 
     ON DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR = msghour
  GROUP BY DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR
  ORDER BY DATE(smsDate) + INTERVAL HOUR(smsDate) HOUR

这将为您提供一个结果集,其中包含该范围内每小时的时间戳和计数。

最后,这个seq_0_to_999999序列表怎么样?我们从零开始得到那些整数?答案是:我们必须安排这样做;这些数字不是内置于MySQL(MariaDB v10 +确实有它们)。

简单的方法是创建一个包含大量整数的表。但这会占用存储空间,所以我们会跳过它。

另一种方法是创建一个包含0-9整数的短表,如下所示:

DROP TABLE IF EXISTS seq_0_to_9;
CREATE TABLE seq_0_to_9 AS
   SELECT 0 AS seq UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
    UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9;

然后我们可以创建一个视图,将该表与自身连接起来,生成1000个这样的组合:

DROP VIEW IF EXISTS seq_0_to_999;
CREATE VIEW seq_0_to_999 AS (
SELECT (a.seq + 10 * (b.seq + 10 * c.seq)) AS seq
  FROM seq_0_to_9 a
  JOIN seq_0_to_9 b
  JOIN seq_0_to_9 c
);

最后,我们可以将1000个数字表连接起来,创建一个视图,生成一百万个这样的组合:

DROP VIEW IF EXISTS seq_0_to_999999;
CREATE VIEW seq_0_to_999999 AS (
SELECT (a.seq + (1000 * b.seq)) AS seq
  FROM seq_0_to_999 a
  JOIN seq_0_to_999 b
);

这是一篇提供有关所有这些内容的更多信息的文章。 http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/