【问题标题】:Return 0 in GROUP BY when COUNT(*) is NULL当 COUNT(*) 为 NULL 时,在 GROUP BY 中返回 0
【发布时间】:2017-01-24 18:24:39
【问题描述】:

这是我原来的查询:

SELECT
    CAST(IndexedDate as varchar),
    COUNT(*) AS Logins
FROM
    Table
WHERE
    EventType = 'Login'
AND IndexedDate > DATEADD(mm, -1, GETDATE())
GROUP BY
    IndexedDate
ORDER BY
    IndexedDate DESC

这会留下空白,例如:

2016-09-13    41
2016-09-12    31
2016-09-09    15
2016-09-08    36

基于this question,我尝试了以下方法,但仍然收到了差距,但最重要的是结果是错误的(数字要高得多):

SELECT
    CAST(IndexedDate as varchar),
    SUM(Case When COUNT(*) Is Null Then 0 Else COUNT(*)  End) AS Logins
FROM
...

我怎样才能让我的结果看起来像这样?

2016-09-13    41
2016-09-12    31
2016-09-11    0
2016-09-10    0
2016-09-09    15
2016-09-08    36

我检查了其他一些问题,但它们都涉及联接或其他不属于我的场景的因素。


更新

基于 cmets,我尝试了 OUTER JOIN。这次迭代终于跑成功了,但是结果有点倒退了……

SELECT
        CAST(a.IndexedDate as varchar) as dt,
        COUNT(*) AS Logins
FROM 
        (
        SELECT *
        FROM Table
        WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
        AND EventType = 'Login'
        ) a
FULL OUTER JOIN (
        SELECT DISTINCT(IndexedDate)
        FROM Table
        WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
        ) b
ON 
        a.IndexedDate = b.IndexedDate
GROUP BY
        b.IndexedDate
ORDER BY
        b.IndexedDate DESC

结果:

2016-09-13    41
2016-09-12    31
(null)    1
(null)    1
2016-09-09    15
2016-09-08    36

我验证了聚合 b 包括缺失的日期。

【问题讨论】:

  • 与日历表的外部联接(保存所有可能的日期)。
  • 啊,有道理。我希望有一个更简单的方法,但我想你不能从无到有。
  • 跟随@jarlh 的评论,SQL 查询非常适合获取现有信息并将其转换为您想要的内容。但是在您的情况下,如果确实缺少日期数据,那么日历表是引入该数据的好方法。
  • 实际上,您使用 SQL Server 中的stackoverflow.com/questions/11141507/… 之类的技术即时创建日期列表。该表 n 可以在日期与您的表连接,以便在需要时获取 count(*)。
  • 回滚标签更改,因为 DBMS总是 相关...

标签: sql count null group-by intersystems-cache


【解决方案1】:

这有效(在 SQL Server 中)

declare @mindt date = (select min(IndexedDate ) from p);
declare @dtrange int = DATEDIFF(day,@mindt,(select max(IndexedDate ) from p));

with MyCte AS
    (select   MyCounter = 0
     UNION ALL
     SELECT   MyCounter + 1
     FROM     MyCte
     where    MyCounter < @dtrange)
select coalesce(IndexedDate , dateadd(d, mycounter, @mindt)) IndexedDate
, count(IndexedDate)
from   MyCte 
left join p
  on dateadd(d,mycounter,@mindt) = p.IndexedDate 
group by coalesce(IndexedDate , dateadd(d, mycounter, @mindt))
option (maxrecursion 0);

我们基本上需要两个主要数字,开始日期和日期范围。

我们为日期范围内的天数构建了一个快速计数器。

然后我们选择日期范围内的每个时间段并分配一个日期和一个值,如果没有,我们创建一个带有DateAdd 的日期并分配 0 作为值。

Here is a functional 示例

【讨论】:

    【解决方案2】:

    所以我将编辑中的聚合翻转到我的原始帖子,现在它正在工作:

    查询

    SELECT
            CAST(a.IndexedDate as varchar) as dt,
            COUNT(EventType) AS Logins
    FROM 
            (
            SELECT DISTINCT(IndexedDate)
            FROM Table
            WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
            ) a
    FULL OUTER JOIN (
            SELECT *
            FROM Table
            WHERE IndexedDate > DATEADD(mm, -1, GETDATE())
            AND EventType = 'Login'
            ) b
    ON 
            a.IndexedDate = b.IndexedDate
    GROUP BY
            a.IndexedDate
    ORDER BY
            a.IndexedDate DESC
    

    结果

    2016-09-13    41
    2016-09-12    31
    2016-09-11    0
    2016-09-10    0
    2016-09-09    15
    2016-09-08    36
    

    请注意,我必须将 COUNT(*) 替换为 COUNT(EventType),这样它就不会从导致 1 的聚合中计算日期。

    【讨论】:

    • 不,1 来自于当你 full outer join 到 b 时,b 中的每一行都会在你的结果集中有一行,这意味着 行数 那个日期实际上是 1,即使你没有来自表 a 的任何数据。您需要将select 中的count(*) 表达式替换为case when a.IndexedDate is null then 0 else count(*) end,以便为这些行正确分配0
    • 我比你快 10 秒!不过谢谢!
    • Scott 您是否为每个 IndexedDate 设置了一个事件,即使它不是登录事件类型?如果您的表中没有特定日期的事件,这不会省略该日期吗?
    • 是的,但正如原始问题中提到的,我确实验证了所有日期都在那里,而且这个表非常庞大,每天记录的事件数以万计。如果缺少日期,那就大错特错了:)
    猜你喜欢
    • 1970-01-01
    • 2014-04-26
    • 2011-04-05
    • 1970-01-01
    • 1970-01-01
    • 2021-11-05
    • 1970-01-01
    • 1970-01-01
    • 2016-03-11
    相关资源
    最近更新 更多