neilrudds
neilrudds

Reputation: 215

SQL Count to include zero values

I have created the following stored procedure that is used to count the number of records per day between a specific range for a selected location:

[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))

but the problem is that the result does not show the days where there are zero records, I pretty sure that it has something to do with my WHERE statement not allowing the zero values to be shown but I do not know how to over come this issue.

Thanks in advance Neil

Upvotes: 8

Views: 19060

Answers (4)

KeithS
KeithS

Reputation: 71565

Not so much the WHERE clause, but the GROUP BY. The query will only return data for rows that exist. That means when you're grouping by the date of the timestamp, only days for which there are rows will be returned. SQL Server can't know from context that you want to "fill in the blanks", and it wouldn't know what with.

The normal answer is a CTE that produces all the days you want to see, thus filling in the blanks. This one's a little tricky because it requires a recursive SQL statement, but it's a well-known trick:

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

Breaking it down, the CTE uses a union that references itself to recursively add one day at a time to the previous date and remember that date as part of the table. If you ran a simple statement that used the CTE and just selected * from it, you'd see a list of dates between start and end. Then, the statement joins this list of dates to the log table based on the log timestamp date, while preserving dates that have no log entries using the left join (takes all rows from the "left" side whether they have matching rows on the "right" side or not). Finally, we group by date and count instead and we should get the answer you want.

Upvotes: 9

Philip Borchert
Philip Borchert

Reputation: 31

I just recently has a similar task and used this as a backdrop to my work. However, as explained by robwilliams I too, couldn't get it KeithS solution to work. Mine task was slightly different I was doing it by hours vs days but I think the solution to the neilrudds question would be

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)

Upvotes: 0

chester
chester

Reputation: 1

Use a left outer join. Such as

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

Upvotes: 0

podiluska
podiluska

Reputation: 51494

When there is no data to count, there is no row to return.

If you want to include empty days as a 0, you need to create a table (or temporary table, or subquery) to store the days, and left join to your query from that.

eg: something like

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)) 

Upvotes: 4

Related Questions