2011-03-01 165 views
3

我試圖返回從MySQL一個結果是由年份和月份分組,這對於每年/月返回計數..的MySQL COUNT - 返回零結果,而不是NULL

這裏是哪裏我開始了:

SELECT YEAR(p.pEndDate) AS pYear, MONTHNAME(p.pEndDate) AS pMonth, count(*) AS pNum 
FROM projects p 
WHERE p.status=3 
GROUP BY YEAR(p.pEndDate), MONTH(p.pEndDate) 

這個SQL基本上做我需要的90%,除非有一個月的計數爲零的情況。例如,在2009年7月實現了零個項目,3狀態,所以我得到:

2008 November 1 
2009 January  2 
2009 February 2 
2009 March 2 
2009 April 1 
2009 May 2 
2009 June 3 
2009 August 2 
2009 September 1 
2009 October 1 
2009 November 2 
2009 December 1 
2010 January 4 
2010 February 1 
2010 March 1 
2010 April 3 
2010 May 3 
2010 June 3 
2010 July 3 
2010 August 3 
2010 September 3 
2010 October 2 
2010 November 2 
2010 December 3 
2011 January 2 
2011 February 1 

注意七月怎麼就是不存在。

所以我就開始做一些研究使用另一臺給力的結果集,包括七月。所以,我創建了一個新表「monthTable」,並添加兩列monthID int主鍵,MONTHNAME VARCHAR(3)。

我已經嘗試了很多不同的方式來使用這張表,從右連接等開始..沒有他們已經取得了成功的結果,實際上幾乎我做的每件事產生與上面相同的結果集。

任何幫助將不勝感激!

+0

查看「datetime-generation」標籤的結果 - 我已經回答了過去對同一個問題的各種解釋。您需要獲取不缺少條目的日期列表,然後將您的數據添加到該列表中。 – 2011-03-01 18:09:39

+0

@OMG小馬 - 這就是我對monthTable所做的事情,但是,當我在monthTable和項目表上使用左連接時,返回相同的結果。 – Steve 2011-03-01 18:20:40

回答

1

我試圖用這個[monthTable表,從右側的許多不同的方式加盟等上..他們都沒有取得成功的結果,實際上幾乎我所做的每件事都產生了與上述相同的結果集。

FROM projects p WHERE p.status=3

我的猜測是,你正在嘗試這樣的事情

FROM projects p 
RIGHT JOIN monthTable m on <join p to m> 
WHERE p.status=3` 

的問題是,WHERE子句將篩選出沒有任何p.status任何記錄值(空)。你需要這樣的過濾器移動到JOIN子句,像這樣

FROM projects p 
RIGHT JOIN monthTable m on <join p to m> AND p.status=3` 

好奇,但如何做喜歡就夠了一個表,尤指MONTHNAME僅爲VARCHAR(3)?

monthID int Primary Key, monthName VARCHAR(3). 

嘗試像這樣代替(一次性)創建它

DROP PROCEDURE IF EXISTS FillMonthsTable; 

delimiter // 
CREATE PROCEDURE FillMonthsTable() 
    LANGUAGE SQL 
    NOT DETERMINISTIC 
    CONTAINS SQL 
    SQL SECURITY DEFINER 
    COMMENT '' 
BEGIN 
    drop table if exists monthsTable; 
    create table monthsTable (theYear int, theMonth int, monthName varchar(20)); 

    SET @x := date('2000-01-01'); 
    REPEAT 
    insert into monthsTable (theyear, themonth, monthname) SELECT year(@x), month(@x), monthname(@x); 
    SET @x := date_add(@x, interval 1 month); 
    UNTIL @x > date('2030-01-01') END REPEAT; 
END// 
delimiter ; 

CALL FillMonthsTable; 

DROP PROCEDURE FillMonthsTable; 

然後使用此查詢(1通到組數據,然後左連接產生0)

SELECT m.theYear, m.theMonth, IFNULL(t.pNum, 0) theCount 
FROM monthsTable m 
LEFT JOIN (
    SELECT YEAR(p.pEndDate) AS pYear, MONTH(p.pEndDate) AS pMonth, count(*) AS pNum 
    FROM projects p 
    WHERE p.status=3 
    GROUP BY YEAR(p.pEndDate), MONTH(p.pEndDate) 
) t on t.pYear = m.theYear and t.pMonth = m.theMonth 
ORDER BY m.theYear, m.theMonth 
+0

謝謝!一個非常好的詳細答案,恐怕我不能答覆你的答覆,因爲我的代表太低。但是,謝謝。我添加了行WHERE m.theYear>'2007'和m.theYear <'2012'來進一步過濾結果。 – Steve 2011-03-01 19:14:26

0

上擴展OMG小馬聲明,你需要一個數字或帳簿桌,包括整數的順序列表,涵蓋您的幾個月和幾年對要查詢所有年份。

Create Table Numbers (Value int not null Primary Key) 
Insert Numbers(Value) Values(1) 
Insert Numbers(Value) Values(2) 
... 
Insert Numbers(Value) Values(12) 
... 
Insert Numbers(Value) Values(2000) 
Insert Numbers(Value) Values(2001) 
... 
Insert Numbers(Value) Values(2011) 
Insert Numbers(Value) Values(2012) 

這將是一次性插入和表將保持不變,直到你需要更多的月份或年份。就這樣,我們現在左加入您的項目表中的數字表:

Select Years.Value As PYear 
    , Month_Name(Date_Add('2000-01-01', Interval Months.Value - 1 MONTH)) As PMonth 
    , Count(P.NonNullableCol) As PNum 
From Numbers As Months 
    Cross Join Numbers As Years 
    Left Join Projects As P 
     On Year(P.PEnddate) = Years.Value 
      And Month(P.PEndDate) = Months.Value 
Where Months.Value Between 1 And 12 
    And Years.Value Between 2008 And 2011 
Group By Years.Value, Months.Value 

加成

每評論,沒有告訴我們的基礎數據的性質。但是,如果有問題的值分別爲日期,沒有日期和時間,然後更快的方法將是一個日曆表,而不是一個Numbers表格。像Numbers表格,這將連續日期覆蓋在你的項目表日期的時間段的靜態表。

Create Table Calendar (DateValue date not null Primary Key) 
Insert Calendar(DateValue) Values('2000-01-01') 
Insert Calendar(DateValue) Values('2000-01-02') 
Insert Calendar(DateValue) Values('2000-01-03') 
... 
Insert Calendar(DateValue) Values('2011-03-01') 

Select Year(C.DateValue) As PYear 
    , Month(C.DateValue) As PMonth 
    , Count(P.NonNullableCol) As PNum 
From Calendar As C 
    Left Join Projects As P 
     On P.PEndDate = C.DateValue 
Where C.DateValue Between '2008-11-01' And '2011-02-28' 
Group By Year(C.DateValue), Month(C.DateValue) 
+0

這是一個討厭的查詢,YEAR(列)和MONTH(列)非SARGable函數 – RichardTheKiwi 2011-03-01 18:36:42

+0

@Richard aka cyberkiwi - 有36行,它表現很好; - >。如果性能出現問題,解決方案是將Numbers表擴展爲日曆表,其中包含範圍內的所有日期,然後按年份和月份分組。 – Thomas 2011-03-01 18:41:17

+0

查看EXPLAIN計劃。在輸出中是36行還是在表中是36行?您擁有的查詢將試圖加入(並且由於功能而進行掃描)兩個表,並在昂貴的加入之後執行GROUPing。我可能是錯的,但是MySQL對CROSS JOIN並不聰明,所以在使用WHERE子句之前,你可能會得到另一個擴展。在基表上分組需要1次掃描,並且應該更快 – RichardTheKiwi 2011-03-01 18:52:43

0

如果你有一個稱爲nums用整數0至9的輔助表,可以產生任何類型的不間斷序列。你的問題不在於計數是空的日期值,它是日期值不存在的。所以說,你想2004年1月和2006年3月間每月數,你可以使用nums表這樣創建一個臨時日期列表:

SELECT DISTINCT ADDDATE('2004-01-01',INTERVAL i.i+j.i+k.i MONTH) AS mydate 
FROM nums i JOIN nums j JOIN nums k ORDER BY mydate LIMIT 27; 

然後爲其他地方所描述你參加你的真實數據日期列表ON(一年=年和月=月)。

這裏有一個類似的查詢我自己的表(MSDS)做了說明:

select year(mydate) theyear, monthname(mydate) themonth, coalesce(c,0) thecount 
from 

(select DISTINCT adddate('2004-01-01',INTERVAL i.i+j.i+k.i MONTH) as mydate 
FROM ints i JOIN ints j join ints k ORDER BY mydate LIMIT 27) datelist 

left join 

(SELECT year(issue_date) as y, month(issue_date) as m, count(*) c FROM msds m where issue_date between '2004-01-01' and '2006-03-01' 
group by y, m) mydata 

on (year(mydate)=y and month(mydate)=m)