【问题标题】:Mysql: Group by Hour, 0 if no dataMysql:按小时分组,如果没有数据则为0
【发布时间】:2015-03-31 12:09:20
【问题描述】:

我有以下疑问:

SELECT count(*) as 'totalCalls', HOUR(`end`) as 'Hour'
FROM callsDataTable 
WHERE company IN (
    SELECT number 
    FROM products 
    WHERE products.id IN (@_PRODUCTS)) 
    AND YEAR(`end`) = @_YEAR AND MONTH(`end`) = @_MONTH 
 group by HOUR(`end`) 

以上查询仅返回拨打电话的时间:

totalCalls  Hour
    2       0
    1       2
    4       7
   98       8
  325       9
  629      10
  824      13
  665      15
  678      16
  665      17
  606      18     
   89      22
    5      23

所需的输出应该是所有时间,如果没有呼叫,则该小时应该是 0 次呼叫,如下所示:

totalCalls  Hour
    0       0
    0       1
    1       2
    0       3
    0       4
    0       5
    0       6
    4       7
   98       8
  325       9
  629      10
    0      11
    0      12
  824      13
    0      14
  665      15
  678      16
  665      17
  606      18
    0      19
    0      20
    0      21
   89      22
    5      23

【问题讨论】:

  • 嗯,一天有24 小时。预期结果应以 Hours 字段中具有 0 的行开头。除非您不关心介于(包括)0:00:000:59:59 之间的电话。

标签: mysql sql time-series aggregate-functions


【解决方案1】:

您需要一个Hour 表,然后使用Hour_table 执行left Outer Join

这将确保返回所有hours。如果hour 不存在于callsDataTable 中,则计数将为0

小时表

create table hours_table (hours int);

insert into hours_table values(0);
insert into hours_table values(1);
 ...
insert into hours_table values(23);

查询:

SELECT count(HOUR(`end`)) as 'totalCalls', HT.Hours as 'Hour'
FROM Hours_table HT left Outer join callsDataTable CD
on HT.Hours = HOUR(`end`)
WHERE company IN (
    SELECT number 
    FROM products 
    WHERE products.id IN (@_PRODUCTS)) 
    AND YEAR(`end`) = @_YEAR AND MONTH(`end`) = @_MONTH 
 group by HT.Hours

【讨论】:

  • 试过了,它不会返回没有数据的所有小时数为 0,而且速度很慢,只有 2.84 秒
  • 我很好奇HOUR(end) = 24。我想这就是世界末日。 :-)
  • @axiac 是对的。您希望 hours_table 包含一个零值。
【解决方案2】:

首先,您的查询可以用更简单的方式表示为:

SELECT COUNT(*) AS totalCalls, HOUR(`end`) AS `Hour`
FROM callsDataTable c
  INNER JOIN products p ON c.company = p.number
    AND p.id IN (@_PRODUCTS)
    AND YEAR(`end`) = @_YEAR AND MONTH(`end`) = @_MONTH
GROUP BY HOUR(`end`) AS `Hour`
ORDER BY `Hour` ASC

使用@NoDisplayName 在their answer 中建议的想法:

CREATE TABLE hours_table (hours INT);

INSERT INTO hours_table VALUES(0), (1), (2), 
    /* put the missing values here */ (23);

您可以加入包含小时数的表格以获得您想要的结果:

SELECT COUNT(*) AS totalCalls, h.hours AS `Hour`
FROM callsDataTable c
  INNER JOIN products p ON c.company = p.number
  RIGHT JOIN hours_table h ON h.hours = HOUR(c.`end`)
    AND p.id IN (@_PRODUCTS)
    AND YEAR(`end`) = @_YEAR AND MONTH(`end`) = @_MONTH
GROUP BY h.hours
ORDER BY h.hours ASC

如果它运行得太慢(而且我确信它非常慢),您应该研究一种使用类似end BETWEEN '2015-01-01 00:00:00' AND '2015-01-31 23:59:59' 的方法,而不是比较YEAR(end)MONTH(end)

可以这样实现:

SET @start = STR_TO_DATE(CONCAT(@_YEAR, '-', @_MONTH, '-01 00:00:00'), '%Y-%m-%d %H:%i:%s');
SET @end   = DATE_SUB(DATE_ADD(@start, INTERVAL 1 MONTH), INTERVAL 1 SECOND);

SELECT ...
...
    AND `end` BETWEEN @start AND @end
...

但这种变化本身并没有帮助。它需要在字段end 上建立索引以带来所需的速度提升:

ALTER TABLE callsDataTable ADD INDEX(end);

在连接条件中使用HOUR(c.end) 是运行缓慢的另一个原因。

可以通过将表hours_table 与第一个查询(的简化版本)产生的结果集相结合来改进:

SELECT IFNULL(totalCalls, 0) AS totalCalls, h.hours AS `Hour`
FROM hours_table h
   LEFT JOIN (
        SELECT COUNT(*) AS totalCalls, HOUR(`end`) as `Hour`
        FROM callsDataTable c
          INNER JOIN products p ON c.company = p.number
            AND p.id IN (@_PRODUCTS)
            AND YEAR(`end`) = @_YEAR AND MONTH(`end`) = @_MONTH
        GROUP BY HOUR(`end`) AS `Hour`
   ) d ON h.hours = d.`Hour`
ORDER BY h.hours ASC

【讨论】:

  • 不错,会试试这个然后回来
【解决方案3】:

在 MySQL 中,从无到有生成值并非易事(通常甚至不可能)。

我建议一个更简单的方法:

  1. 在客户端代码中生成24 条目列表(totalCallsHour),其中0totalCalls,小时(从023)为Hour。在任何编程语言中,这都是一项简单的任务。
  2. 运行您已有的查询,获取它返回的值并使用它们覆盖上一步生成的空值。
  3. 享受吧。

【讨论】:

    猜你喜欢
    • 2020-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-03
    • 2021-06-03
    • 1970-01-01
    相关资源
    最近更新 更多