mysqljoincountfiscal

MySQL - count by month (including missing records)


I have this SELECT:

SELECT
  DATE_FORMAT(`created`, '%Y-%m') as byMonth,
  COUNT(*) AS Total 
FROM 
  `qualitaet`
WHERE
  `created` >= MAKEDATE(year(now()-interval 1 year),1) + interval 5 month
AND
  `status`=1
GROUP BY 
  YEAR(`created`), MONTH(`created`)
ORDER BY 
  YEAR(`created`) ASC

and get this result:

| byMonth | Total |
| 2015-06 |   2   |
| 2015-09 |  12   |
| 2015-10 |   3   |
| 2015-12 |   8   |
| 2016-01 |   1   |

see SQL-Fiddle here

The WHERE clause is important because i need it as current fiscal year starting on June, 1 in my example.

As you can see, i have no records for Jul, Aug and Nov. But i need this records with zero in Total.

So my result should look like this:

| byMonth | Total |
| 2015-06 |   2   |
| 2015-07 |   0   |
| 2015-08 |   0   |
| 2015-09 |  12   |
| 2015-10 |   3   |
| 2015-11 |   0   |
| 2015-12 |   8   |
| 2016-01 |   1   |

is there a way to get this result?


Solution

  • You need to generate all the wanted dates, and then left join your data to the dates. Note also that it is important to put some predicates in the left join's ON clause, and others in the WHERE clause:

    SELECT
      CONCAT(y, '-', LPAD(m, 2, '0')) as byMonth,
      COUNT(`created`) AS Total 
    FROM (
      SELECT year(now())     AS y UNION ALL
      SELECT year(now()) - 1 AS y 
    ) `years`
    CROSS JOIN (
      SELECT  1 AS m UNION ALL
      SELECT  2 AS m UNION ALL
      SELECT  3 AS m UNION ALL
      SELECT  4 AS m UNION ALL
      SELECT  5 AS m UNION ALL
      SELECT  6 AS m UNION ALL
      SELECT  7 AS m UNION ALL
      SELECT  8 AS m UNION ALL
      SELECT  9 AS m UNION ALL
      SELECT 10 AS m UNION ALL
      SELECT 11 AS m UNION ALL
      SELECT 12 AS m
    ) `months`
    LEFT JOIN `qualitaet` q
    ON YEAR(`created`) = y 
      AND MONTH(`created`) = m
      AND `status` = 1
    WHERE STR_TO_DATE(CONCAT(y, '-', m, '-01'), '%Y-%m-%d') 
        >= MAKEDATE(year(now()-interval 1 year),1) + interval 5 month
      AND STR_TO_DATE(CONCAT(y, '-', m, '-01'), '%Y-%m-%d') 
        <= now()
    GROUP BY y, m
    ORDER BY y, m
    

    How does the above work?

    A note on performance

    The above makes heavy use of string operations and date time arithmetic in your ON and WHERE predicates. This isn't going to perform for lots of data. In that case, you should better pre-truncate and index your year-months in the qualitaet table, and operate only on those values.