2012-09-11 88 views
6

我已經創建了用於計數的特定範圍之間每天記錄數爲選定位置以下存儲過程:SQL計數爲包括零值

[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語句有關,不允許顯示零值,但我不知道如何解決這個問題。

在此先感謝 尼爾

+0

如果除去聚集你得到的行後面,你會期待什麼呢? –

+0

正如其他人發佈的那樣,您首先需要一個實際的日期列表(我自己更喜歡一個永久性日曆文件 - 如果企業的財務日曆與標準本地日曆不一致,則它們特別有用)。但是,請不要**使用'BETWEEN',_especially_用於日期/時間/時間戳記值。我們自己的Aaron Bertrand擁有[博客文章](http://sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/what-do-between-and-the-devil-have-in-common)。 aspx)有關SQL Server在使用此子句時遇到的特定問題。 –

回答

9

與其說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(*), 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 

其分解,熱膨脹係數使用引用本身工會在時間的遞歸添加1天以前的日期並記住該日期作爲表格的一部分。如果您運行了一個使用CTE並從中選擇*的簡單語句,您會看到開始和結束之間的日期列表。然後,語句根據日誌時間戳日期將該日期列表加入到日誌表中,同時保留使用左側連接沒有日誌條目的日期(從「左側」獲取所有行,無論它們在「正確「或不)。最後,我們按日期和次數進行分組,並且我們應該得到您想要的答案。

+0

嗨,感謝您解釋清楚的答覆,它幫助我瞭解問題的原因。我已經嘗試了與我的表格等相關的示例代碼,並返回與以前完全相同的結果,即沒有返回零值。我想我錯過了一些東西:s – neilrudds

+0

你可能是。如果您使用的是左連接,請確保首先列出CTE,如果您使用的是正確連接,請確保CTE列在最後*。 – KeithS

+0

+1爲一個很好的答案,但這並不能解決問題,因爲通過計算*,您將總是返回至少1條記錄,因爲CTE中的行。我試圖編輯正確的答案,即COUNT(HL_Logs.Time_Stamp),它將在沒有記錄的日期返回0,但它被拒絕。這樣的恥辱。這就是爲什麼我不打擾經常使用StackOverflow。 – robwilliams

4

當沒有數據來算,也沒有行返回。

如果要將空白天數設置爲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)) 
0

使用左外連接。如

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

我剛剛也有類似的任務,並以此爲背景,我的工作。然而,正如Robwilliams所解釋的,我也無法使KeithS解決方案工作。煤礦的任務是略有不同的我被時間VS天這樣做,但我認爲,解決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)