2012-09-11 18 views
6

ho creato la seguente stored procedure che viene utilizzato per contare il numero di record al giorno tra una gamma specifica per una posizione selezionata:Conte SQL per includere i valori zero

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

ma il problema è che il risultato non mostra i giorni in cui ci sono zero record, sono abbastanza sicuro che abbia qualcosa a che fare con la mia dichiarazione WHERE che non permette di mostrare i valori zero ma non so come superare questo problema.

Grazie in anticipo Neil

+0

Se si rimuove l'aggregazione si ottiene di nuovo le righe che ci si aspetta? –

+0

Come gli altri hanno pubblicato, è necessario un elenco effettivo dei giorni, in primo luogo (preferisco un file di calendario permanente me stesso - sono particolarmente utili se il calendario fiscale di un'azienda non coincide con il calendario locale standard). Tuttavia, per favore ** mai ** usa 'BETWEEN', _especially_ per i valori di data/ora/data/ora. Il nostro stesso Aaron Bertrand ha un [post di blog] (http://sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/what-do-between-and-the-devil-have-in-common. aspx) sui problemi particolari che SQL Server ha quando utilizza questa clausola. –

risposta

9

Non tanto la clausola WHERE, ma il GROUP BY. In poche parole, la query restituirà solo i dati per le righe esistenti. Ciò significa che quando stai raggruppando per la data del timestamp, verranno restituiti solo i giorni per i quali ci saranno righe. SQL Server non può sapere dal contesto che si desidera "riempire gli spazi vuoti" e non saprebbe con cosa.

La risposta normale è un CTE che produce tutti i giorni che si desidera vedere, riempiendo così gli spazi vuoti. Questo è un po 'difficile perché richiede un'istruzione SQL ricorsiva, ma è un trucco ben noto:

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 

rottura verso il basso, il CTE utilizza un sindacato che si fa riferimento a aggiungere ricorsivamente un giorno alla volta al data precedente e ricorda quella data come parte della tabella. Se hai eseguito una semplice istruzione che utilizzava il CTE e ne selezionavi solo *, vedresti un elenco di date tra inizio e fine. Quindi, l'istruzione unisce questo elenco di date alla tabella del registro in base alla data del timestamp del registro, preservando le date che non hanno voci di registro che utilizzano il join sinistro (tutte le righe dal lato "sinistro" se hanno righe corrispondenti su " giusto "lato o no). Infine, raggruppiamo per data e conteggio e dovremmo ottenere la risposta desiderata.

+0

Ciao grazie per la tua risposta ben spiegata, mi ha aiutato a capire le ragioni del problema. Ho provato il nostro codice di esempio relativo alle mie tabelle ecc e restituisce gli stessi risultati identici di prima, cioè non ci sono valori zero restituiti.Penso di aver perso qualcosa: s – neilrudds

+0

Probabilmente lo sei. Assicurati che il CTE sia elencato per primo se stai usando un join a sinistra e * last * se stai usando un join a destra. – KeithS

+0

+1 per una buona risposta, ma questo non risolve il problema poiché contando * si ritornerà sempre almeno 1 record a causa della riga nel CTE. Ho provato a modificare la risposta corretta, ad esempio COUNT (HL_Logs.Time_Stamp) che restituirà 0 per quelle date senza record, tuttavia è stato rifiutato. Che peccato. Questo è il motivo per cui non mi preoccupo spesso di interagire con StackOverflow. – robwilliams

4

Quando non ci sono dati per contare, non v'è nessuna riga di tornare.

Se si desidera includere giorni vuoti come 0, è necessario creare una tabella (o tabella temporanea o sottoquery) per archiviare i giorni e lasciare il join alla query da quello.

esempio: qualcosa di simile

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

Utilizzare un giunto esterno sinistro. Ad esempio

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

Recentemente ho un compito simile e ho usato questo come sfondo per il mio lavoro. Tuttavia, come spiegato da Robwilliams anch'io, non riuscivo a far funzionare la soluzione KeithS. Il mio compito era leggermente diverso lo facevo da ore vs giorni, ma penso che la soluzione alla questione neilrudds sarebbe

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