Reputation: 409
I'm using a MySQL database.
I want to count all row in a month from a table. This script produces for me a correct output:
SELECT Count(*) as size, DATE_FORMAT(mydate, "%Y-%m") as mydate
FROM Table
GROUP BY DATE_FORMAT(mydate, "%Y-%m") ASC
The output is a table with a number and a date attribute. For example:
10 | 2011-01
40 | 2011-03
20 | 2011-05
My problem is that the month which hasn't any row in my database won't appear at the output. Which is normal at now.
I would like to write an sql query which produces an output like this:
10 | 2011-01
0 | 2011-02
40 | 2011-03
0 | 2011-04
20 | 2011-05
I would like to use just the pure SQL language for this problem if it is possible.
Could anyone help me with this SQL script?
Upvotes: 2
Views: 460
Reputation: 3824
See my fiddle.
Solution is based on calendar tables article.
Full listing is below:
/*Build a calendar*/
CREATE TABLE `Int` ( i tinyint );
CREATE TABLE Calendar (dt DATE NOT NULL PRIMARY KEY);
CREATE TABLE Report(
size INT NOT NULL,
dt CHAR(7) NOT NULL
);
INSERT INTO `Int` VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
INSERT INTO Calendar (dt)
SELECT DATE('2010-01-01') + INTERVAL a.i*10000 + b.i*1000 + c.i*100 + d.i*10 + e.i DAY
FROM `Int` a
JOIN `Int` b
JOIN `Int` c
JOIN `Int` d
JOIN `Int` e
WHERE (a.i*10000 + b.i*1000 + c.i*100 + d.i*10 + e.i) <= 11322
ORDER BY 1;
/*For simplicity I work with your report as ready-to-use table*/
/*10 | 2011-01
40 | 2011-03
20 | 2011-05*/
INSERT INTO Report VALUES
(10, '2011-01'),
(40, '2011-03'),
(20, '2011-05');
/*
Select data from your report.
Also select dummy data with zero sizes, to pad the dates.
After union, we leave only data with max size value
*/
SELECT MAX(a.size) AS size, a.mydate
FROM
(
(
SELECT size, dt AS mydate
FROM Report
) UNION ALL
(
SELECT DISTINCT 0 AS size, DATE_FORMAT(dt, "%Y-%m") AS mydate
FROM Calendar
WHERE dt BETWEEN '2011-01-01' AND '2011-12-01'
)
) AS a
GROUP BY a.mydate
ORDER BY a.mydate
Results are
size| mydate
------------
10 | 2011-01
0 | 2011-02
40 | 2011-03
0 | 2011-04
20 | 2011-05
0 | 2011-06
0 | 2011-07
0 | 2011-08
0 | 2011-09
0 | 2011-10
0 | 2011-11
0 | 2011-12
Upvotes: 2
Reputation: 1414
I'd recommend creating a permanent dates table and left joining to that (its pretty handy), or you could make a temp one filled with dates:
CREATE TEMPORARY TABLE daterange (dte DATE);
SET @counter := -1;
WHILE (@counter < DATEDIFF(DATE(_todate), DATE(_fromdate))) DO
INSERT daterange VALUES (DATE_ADD(_fromdate, INTERVAL @counter:=@counter + 1 DAY));
END WHILE;
and then using your query as a subquery, do a left join to get the missing dates
SELECT ifnull(data.thecount,0) as theCount, daterange.dte FROM
daterange LEFT JOIN
(
SELECT COUNT(*) AS theCount, DATE_FORMAT(dates.dte, "%Y-%m") as mydate
FROM table
GROUP BY DATE_FORMAT(dates.dte, "%Y-%m") ASC
) DATA
ON daterange.dte = DATA.mydate
ORDER BY daterange.dte asc
drop your temp table when finished:
DROP TEMPORARY TABLE daterange
EDIT: The left join was very very wrong, fixed here. You may need to play around with the formatting
Upvotes: 2