【问题标题】:SQL Count to include zero values包含零值的 SQL 计数
【发布时间】:2012-09-04 13:26:59
【问题描述】:

我创建了以下存储过程,用于计算所选位置的特定范围内每天的记录数:

[dbo].[getRecordsCount] 
@LOCATION as INT,
@BEGIN as datetime,
@END as datetime

SELECT
ISNULL(COUNT(*), 0) AS counted_leads, 
CONVERT(VARCHAR, DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp)), 3) as TIME_STAMP 
FROM HL_Logs
WHERE Time_Stamp between @BEGIN and @END and ID_Location = @LOCATION
GROUP BY DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp))

但问题是结果没有显示记录为零的日子,我很确定这与我的 WHERE 语句不允许显示零值有关,但我不知道如何结束来这个问题。

提前致谢 尼尔

【问题讨论】:

  • 如果你删除聚合,你会得到你期望的行吗?
  • 正如其他人发布的那样,您首先需要一个实际的日期列表(我自己更喜欢永久日历文件 - 如果企业的财务日历与标准不符,它们特别有用当地日历)。但是,请切勿使用BETWEEN尤其是用于日期/时间/时间戳值。我们自己的 Aaron Bertrand 有一个 blog post,关于 SQL Server 在使用此子句时遇到的特定问题。

标签: sql sql-server-2008 count


【解决方案1】:

与其说是 WHERE 子句,不如说是 GROUP BY。该查询将只返回存在的行的数据。这意味着当您按时间戳日期分组时,只会返回有行的日期。 SQL Server 无法从上下文中知道您想“填空”,它也不知道用什么。

通常的答案是 CTE,它会产生您想看到的所有日子,从而填补空白。这个有点棘手,因为它需要递归 SQL 语句,但这是一个众所周知的技巧:

WITH CTE_Dates AS
(
    SELECT @START AS cte_date
    UNION ALL
    SELECT DATEADD(DAY, 1, cte_date)
    FROM CTE_Dates
    WHERE DATEADD(DAY, 1, cte_date) <= @END
)
SELECT
cte_date as TIME_STAMP,
ISNULL(COUNT(HL_Logs.Time_Stamp), 0) AS counted_leads, 
FROM CTE_Dates
LEFT JOIN HL_Logs ON DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp)) = cte_date
WHERE Time_Stamp between @BEGIN and @END and ID_Location = @LOCATION
GROUP BY cte_date

分解后,CTE 使用一个引用自身的联合来递归地一次将一天添加到前一个日期,并将该日期作为表格的一部分记住。如果您运行一个使用 CTE 的简单语句并从中选择 *,您会看到开始和结束之间的日期列表。然后,该语句根据日志时间戳日期将此日期列表连接到日志表,同时使用左连接保留没有日志条目的日期(从“左侧”获取所有行,无论它们在“右”侧与否)。最后,我们改为按日期分组并计数,我们应该会得到您想要的答案。

【讨论】:

  • 您好,感谢您详细解释的回复,它帮助我了解了问题的原因。我已经尝试了与我的表等相关的示例代码,它返回的结果与以前完全相同,即没有返回零值。我想我错过了一些东西:s
  • 你可能是。如果您使用左连接,请确保 CTE 列在第一位,如果您使用右连接,请确保 CTE 列在前面。
  • +1 是一个很好的答案,但这并不能解决问题,因为通过计数 * 由于 CTE 中的行,您将始终返回至少 1 条记录。我试图编辑正确的答案,即 COUNT(HL_Logs.Time_Stamp) 它将为那些没有记录的日期返回 0 但是它被拒绝了。真可惜。这就是我不经常使用 StackOverflow 的原因。
  • @robwilliams 我做了同样的修改,希望不会被拒绝
【解决方案2】:

当没有数据要统计时,就没有要返回的行。

如果您想将空天包含为 0,则需要创建一个表(或临时表或子查询)来存储天数,并从中左连接到您的查询。

例如:类似的东西

SELECT 
    COUNT(*) AS counted_leads,  
    CONVERT(VARCHAR, DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp)), 3) as TIME_STAMP  
    FROM 
        TableOfDays
           left join
        HL_Logs 
           on TableOfDays.Date = convert(date,HL_Logs.Time_Stamp)
           and ID_Location = @LOCATION 
    WHERE TableOfDays.Date between @BEGIN and @END 
    GROUP BY DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp)) 

【讨论】:

    【解决方案3】:

    使用左外连接。比如

    select count(stuff_ID), extra_NAME 
    from dbo.EXTRAS 
    left outer join dbo.STUFF on suff_EXTRA = extra_STUFF 
    group by extra_NAME 
    

    【讨论】:

      【解决方案4】:

      我最近有一个类似的任务,并以此作为我工作的背景。但是,正如 robwilliams 所解释的那样,我也无法让 KeithS 解决方案发挥作用。我的任务与我的工作时间略有不同,但我认为 neilrudds 问题的解决方案是

      DECLARE @Start as DATETIME
             ,@End as DATETIME
             ,@LOCATION AS INT;
      
      
      WITH CTE_Dates AS
      (
          SELECT @Start AS cte_date, 0 as 'counted_leads'
          UNION ALL
          SELECT DATEADD(DAY, 1, cte_date) as cte_date, 0 AS 'counted_leads'
          FROM CTE_Dates  
          WHERE DATEADD(DAY, 1, cte_date) <= @End
      )
      SELECT cte_date AS 'TIME_STAMP'
            ,COUNT(HL.ID_Location) AS 'counted_leads'
      FROM CTE_Dates 
      LEFT JOIN HL_Logs AS HL ON CAST(HL.Time_Stamp as date) = CAST(cte_date as date)
      AND DATEPART(day, HL.Time_Stamp) = DATEPART(day,cte_date)
      AND HL.ID_Location = @LOCATION
      group by cte_date
      OPTION (MAXRECURSION 0)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-27
        • 1970-01-01
        • 2015-03-27
        • 2021-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-30
        相关资源
        最近更新 更多