Mysql: Group by Hour, 0 if no data

前端 未结 3 601
时光说笑
时光说笑 2021-01-15 11:08

I have the following query:

SELECT count(*) as \'totalCalls\', HOUR(`end`) as \'Hour\'
FROM callsDataTable 
WHERE company IN (
    SELECT number 
    FROM pr         


        
相关标签:
3条回答
  • 2021-01-15 11:32

    You need a Hour table and then do a left Outer Join with the Hour_table.

    Which will ensure that all hours will be returned. If hour doesn't exists in callsDataTable then count will be 0.

    Hours Table

    create table hours_table (hours int);
    
    insert into hours_table values(0);
    insert into hours_table values(1);
     ...
    insert into hours_table values(23);
    

    Query:

    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 讨论(0)
  • 2021-01-15 11:36

    First, your query can be expressed in a simpler way as:

    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
    

    Using the idea suggested by @NoDisplayName in their answer:

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

    You can join the table that contains the hours to get the results you want:

    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
    

    If it runs too slow (and I'm sure it is very slow) you should investigate a way to use something like end BETWEEN '2015-01-01 00:00:00' AND '2015-01-31 23:59:59' instead of comparing YEAR(end) and MONTH(end).

    It can be accomplished like this:

    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
    ...
    

    But this change doesn't help by itself. It needs an index on field end to bring the desired speed improvement:

    ALTER TABLE callsDataTable ADD INDEX(end);
    

    Using HOUR(c.end) in the join condition is another reason to run slowly.

    It can be improved by joining the table hours_table with the result set produced by the (simplified version of the) first query:

    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
    
    0 讨论(0)
  • 2021-01-15 11:55

    The generation of values from nothing is not an easy job (usually it's not even possible) in MySQL.

    I suggest a simpler approach:

    1. generate in the client code a list of 24 entries (totalCalls, Hour) with 0 as totalCalls and the hours (from 0 to 23) as Hour. This is an easy task in any programming language.
    2. run the query you already have, get the values it returns and use them to overwrite the empty values generated on the previous step.
    3. enjoy.
    0 讨论(0)
提交回复
热议问题