2012-09-11 27 views
6

He creado el siguiente procedimiento almacenado que se utiliza para contar el número de registros por día entre una gama específica para una ubicación seleccionada:Conde SQL para incluir los valores cero

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

pero el problema es que el resultado no muestra los días en los que no hay registros, estoy bastante seguro de que tiene algo que ver con mi declaración WHERE que no permite que se muestren los valores cero, pero no sé cómo superar este problema.

Gracias de antemano Neil

+0

Si se quita la agregación se puede conseguir las filas más atrás que se puede esperar? –

+0

Como han publicado los demás, primero necesita una lista real de los días (prefiero un archivo de calendario permanente, son especialmente útiles si el calendario fiscal de una empresa no coincide con el calendario local estándar). Sin embargo, ** nunca ** utilice 'BETWEEN', _especialmente_ para valores de fecha/hora/indicación de fecha y hora. Nuestro propio Aaron Bertrand tiene una [publicación de blog] (http://sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/what-do-between-and-the-devil-have-in-common. aspx) sobre los problemas particulares que tiene SQL Server al usar esta cláusula. –

Respuesta

9

No tanto la cláusula WHERE, pero el GROUP BY. En pocas palabras, la consulta solo devolverá los datos de las filas que existen. Esto significa que cuando se agrupa por fecha de la marca de tiempo, solo se devolverán los días para los que haya filas. SQL Server no puede saber por contexto que desea "completar los espacios en blanco", y no sabría con qué.

La respuesta normal es un CTE que produce todos los días que desea ver, completando así los espacios en blanco. Éste es un poco difícil, ya que requiere una instrucción SQL recursivo, pero es un truco muy conocido:

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 

Descomponiéndola, el CTE utiliza una unión que hace referencia a sí mismo para agregar de forma recursiva un día a la hora de la fecha anterior y recuerda esa fecha como parte de la tabla. Si ejecutó una declaración simple que usó el CTE y simplemente seleccionó * de él, verá una lista de fechas entre el inicio y el final. Luego, la declaración une esta lista de fechas a la tabla de registro según la fecha de registro de fecha y hora, mientras conserva las fechas que no tienen entradas de registro usando la combinación izquierda (toma todas las filas del lado "izquierdo" si tienen filas coincidentes en el " "lado derecho" o no). Finalmente, agrupamos por fecha y cuenta en su lugar y deberíamos obtener la respuesta que desea.

+0

Hola, gracias por tu respuesta bien explicada, me ha ayudado a entender las razones del problema. He probado nuestro código de muestra relevante para mis tablas, etc., y devuelve exactamente los mismos resultados que antes, es decir, no se devuelve ningún valor cero.Creo que me he perdido algo: s – neilrudds

+0

Probablemente eres. Asegúrese de que el CTE esté en la lista primero si está utilizando una combinación de la izquierda y * la última * si está utilizando una combinación de la derecha. – KeithS

+0

+1 para obtener una respuesta excelente, pero esto no resuelve el problema ya que al contar * siempre devolverá al menos 1 registro debido a la fila en el CTE. Traté de editar a la respuesta correcta, es decir, COUNT (HL_Logs.Time_Stamp) que devolverá 0 para esas fechas sin registros, sin embargo, fue rechazado. Es una pena. Es por eso que no me molesto en involucrarme con StackOverflow a menudo. – robwilliams

4

Cuando no hay datos para contar, no hay filas que devolver.

Si desea incluir días vacíos como un 0, necesita crear una tabla (o tabla temporal o subconsulta) para almacenar los días, y la izquierda se unió a su consulta a partir de eso.

por ejemplo: algo así como

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

Use una combinación externa izquierda. Tal como

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

Hace poco tuve una tarea similar y la utilicé como telón de fondo para mi trabajo. Sin embargo, como explicaba robwilliams yo también, no pude conseguir que la solución KeithS funcionara. tarea mía era un poco diferente que lo hacía por horas vs días pero creo que la solución a la cuestión neilrudds sería

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)