【问题标题】:MySQL COUNT - return zero result, not NULLMySQL COUNT - 返回零结果,而不是 NULL
【发布时间】:2011-03-01 18:06:48
【问题描述】:

我正在尝试从 MySql 返回一个按年和月分组的结果集,并且每个年/月都会返回一个计数..

这是我开始的地方:

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

注意七月是怎么回事。

所以我开始做一些研究,使用另一个表格来强制结果集中包含 7 月。所以我创建了一个新表'monthTable'并添加了两列monthID int Primary Key,monthName VARCHAR(3)。

我尝试了许多不同的方法来使用这个表,从 RIGHT JOIN 开始等等。他们都没有产生成功的结果,事实上我所做的几乎所有事情都会产生与上面相同的结果集。

任何帮助将不胜感激!

【问题讨论】:

  • 查看“datetime-generation”标签的结果——我过去曾回答过对同一问题的各种解释。您需要获取一个不缺少条目的日期列表,并将您的数据 LEFT JOIN 加入其中。
  • @OMG Ponies - 这是我对月份表所做的,但是,当我在月份表和项目表上使用左连接时,会返回相同的结果。

标签: mysql sql datetime-generation


【解决方案1】:

我已经尝试了许多不同的方法来使用这个 [monthTable] 表,从 RIGHT JOIN 开始等等.. 他们都没有产生成功的结果,事实上我所做的几乎所有事情都会产生与上面相同的结果集。

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-pass 对数据进行分组,然后左连接产生 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

【讨论】:

  • 谢谢!一个很好的详细答案,由于我的代表太低,恐怕我无法为您的回复 +1。但是,谢谢。我添加了行 WHERE m.theYear > '2007' AND m.theYear
【解决方案2】:

如果您有一个名为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)

【讨论】:

    【解决方案3】:

    扩展 OMG Ponies 语句,您需要一个 Numbers 或 Tally 表,其中包含一个连续的整数列表,涵盖您要查询的所有年份的月份和年份。

    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 )
    

    这将是一次性插入,并且该表将保持静态,直到您需要更多月或数年。有了这个,我们现在将您的 Projects 表左连接到 Numbers 表:

    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
    

    加法

    对于每个 cmets,我们没有被告知基础数据的性质。但是,如果所讨论的值是日期而不是日期和时间,那么更快的方法是日历表而不是数字表。与 Numbers 表一样,这将是一个包含 Projects 表中日期时间段的连续日期静态表。

    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 )
    

    【讨论】:

    • 这是一个讨厌的查询,带有 YEAR(column) 和 MONTH(column) 非 SARGable 函数
    • @Richard aka cyberkiwi - 有 36 行,它表现出色 ;->。如果性能是一个问题,解决方案是将 Numbers 表扩展为日历表,其中包含该范围内的所有日期,然后按年份和月份分组。
    • 检查 EXPLAIN 计划。输出中的 36 行还是表中的 36 行?您拥有的查询将尝试连接(并根据功能进行扫描)这两个表,并在昂贵的连接之后执行 GROUPing。我可能是错的,但是 MySQL 对 CROSS JOIN 并不聪明,所以在它使用 WHERE 子句之前你可能会得到另一个扩展。基表上的 Group by 需要 1 遍扫描,应该更快
    • @Richard aka cyberkiwi - 与表中的 36 行一样。我们没有被告知基础数据的性质。如果数据是日期和时间,则解决方案显然与仅是日期不同。如果 MySQL 太愚蠢而无法意识到我只要求 Numbers 表中的 16 行,那又是另一个问题(当然可以通过切换产品来解决;->)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-04
    • 1970-01-01
    • 2011-12-19
    • 1970-01-01
    • 1970-01-01
    • 2021-09-13
    相关资源
    最近更新 更多