Generate a list of numbers from 1 to 12 first to save all month s. Then do LEFT JOIN on Hires to make sure all missing months are counted. Then use conditional aggregation for totals:
SQL Fiddle
;WITH CteMonths AS( SELECT * FROM(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12) )t(N) ) SELECT Month = DATENAME(MONTH, DATEADD(MONTH, N-1,0)), Cleaned = SUM(CASE WHEN h.Status = 'Cleaned' THEN 1 ELSE 0 END), Closed = SUM(CASE WHEN h.Status = 'Closed' THEN 1 ELSE 0 END), Unclean = SUM(CASE WHEN h.Status = 'Unclean' THEN 1 ELSE 0 END), Total = SUM(CASE WHEN h.Status IN('Cleaned', 'Unclean') THEN 1 ELSE 0 END) FROM CteMonths m LEFT JOIN Hires h ON mN = MONTH(h.HireDate)
If you want to enable YEAR:
SQL Fiddle
;WITH CteMonths AS( SELECT * FROM(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12) )t(N) ), CteYears(yr) AS( SELECT DISTINCT YEAR(HireDate) FROM Hires ), CteAllDates(dt) AS( SELECT DATEADD(MONTH, mN - 1, DATEADD(YEAR, y.yr - 1900, 0)) FROM CteMonths m CROSS JOIN CteYears y ) SELECT Year = YEAR(d.dt), Month = DATENAME(MONTH, d.dt), Cleaned = SUM(CASE WHEN h.Status = 'Cleaned' THEN 1 ELSE 0 END), Closed = SUM(CASE WHEN h.Status = 'Closed' THEN 1 ELSE 0 END), Unclean = SUM(CASE WHEN h.Status = 'Unclean' THEN 1 ELSE 0 END), Total = SUM(CASE WHEN h.Status IN('Cleaned', 'Unclean') THEN 1 ELSE 0 END) FROM CteAllDates d LEFT JOIN Hires h ON MONTH(d.dt) = MONTH(h.HireDate) AND YEAR(d.dt) = YEAR(h.HireDate) GROUP BY YEAR(d.dt), MONTH(d.dt), DATENAME(MONTH, d.dt) ORDER BY YEAR(d.dt), MONTH(d.dt)
If you want to filter the year, say @year = 2015 , you can replace the previous ctes as follows:
;WITH CteMonths AS( SELECT * FROM(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12) )t(N) ), CteAllDates(dt) AS( SELECT DATEADD(MONTH, mN - 1, DATEADD(YEAR, @year - 1900, 0)) FROM CteMonths m )...