2011-05-05 19 views
26

Tengo una aplicación que necesita para mostrar un gráfico de barras para la actividad en los últimos 30 días. El gráfico debe mostrar todos los días, incluso si no hay actividad durante el día.SQL Server: Cómo seleccionar todos los días en un intervalo de fechas, aunque no existen datos para algunos días

por ejemplo:

DATE  COUNT 
================== 
1/1/2011 5 
1/2/2011 3 
1/3/2011 0 
1/4/2011 4 
1/5/2011 0 
etc.... 

que podía hacer el procesamiento posterior después de la consulta para averiguar qué fechas se han perdido y añadir ellos, pero se preguntaba si hay una manera más fácil de hacerlo en SQL Server. Muchas gracias

+4

Consejo: los números (también conocido como fecha) –

Respuesta

34

Se puede utilizar una CTE recursiva para construir su lista de 30 días, y luego unirse a que a sus datos

--test 
select cast('05 jan 2011' as datetime) as DT, 1 as val into #t 
union all select CAST('05 jan 2011' as datetime), 1 
union all select CAST('29 jan 2011' as datetime), 1 

declare @start datetime = '01 jan 2011' 
declare @end datetime = dateadd(day, 29, @start) 

;with amonth(day) as 
(
    select @start as day 
     union all 
    select day + 1 
     from amonth 
     where day < @end 
) 
select amonth.day, count(val) 
    from amonth 
    left join #t on #t.DT = amonth.day 
group by amonth.day 


>> 

2011-01-04 00:00:00.000 0 
2011-01-05 00:00:00.000 2 
2011-01-06 00:00:00.000 0 
2011-01-07 00:00:00.000 0 
2011-01-08 00:00:00.000 0 
2011-01-09 00:00:00.000 0 
... 
+2

tabla 1 para el uso de un CTE recursiva para obtener un conjunto de valores. –

+2

Esto no funciona si el número de días es mayor que 100. Me sale el error "nivel de recursividad máxima es de 100". Sí, la respuesta responde a la pregunta, pero puede conducir a errores. –

+4

Entonces el problema no es lo mismo que el de la OP que tiene una ventana de 30 artículo absoluta para el que esta solución está muy bien y no error. Si desea aumentar el límite de recursión, simplemente dígale que lo haga; 'opción (maxrecursion 100)' –

1

Defina una tabla estática que contenga fechas o cree una tabla temporal \ tabla variable sobre la marcha para almacenar cada fecha entre (e incluyendo) las fechas mínimas y máximas en la tabla de actividades con la que está trabajando.

Use una combinación externa entre las dos tablas para asegurarse de que cada fecha en la tabla de fechas se refleja en la salida.

Si se utiliza una tabla de fechas estática es probable que desee limitar el intervalo de fechas que se emite únicamente al rango necesario en el gráfico.

0

create a numbers table y usarlo como:

declare @DataTable table (DateColumn datetime) 
insert @DataTable values ('2011-01-09') 
insert @DataTable values ('2011-01-10') 
insert @DataTable values ('2011-01-10') 
insert @DataTable values ('2011-01-11') 
insert @DataTable values ('2011-01-11') 
insert @DataTable values ('2011-01-11') 

declare @StartDate datetime 
SET @StartDate='1/1/2011' 

select 
    @StartDate+Number,SUM(CASE WHEN DateColumn IS NULL THEN 0 ELSE 1 END) 
    FROM Numbers 
     LEFT OUTER JOIN @DataTable ON [email protected]+Number 
    WHERE Number>=1 AND Number<=15 
    GROUP BY @StartDate+Number 

SALIDA:

----------------------- ----------- 
2011-01-02 00:00:00.000 0 
2011-01-03 00:00:00.000 0 
2011-01-04 00:00:00.000 0 
2011-01-05 00:00:00.000 0 
2011-01-06 00:00:00.000 0 
2011-01-07 00:00:00.000 0 
2011-01-08 00:00:00.000 0 
2011-01-09 00:00:00.000 1 
2011-01-10 00:00:00.000 2 
2011-01-11 00:00:00.000 3 
2011-01-12 00:00:00.000 0 
2011-01-13 00:00:00.000 0 
2011-01-14 00:00:00.000 0 
2011-01-15 00:00:00.000 0 
2011-01-16 00:00:00.000 0 

(15 row(s) affected) 
0

Tal vez algo como esto: Crear DaysTable countaining los 30 días. Y DataTable que contiene la columna "día" y la columna "recuento". Y luego la izquierda únete a ellos.

WITH DaysTable (name) AS (
     SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 -- .. And so on to 30 
    ), 
    DataTable (name, value) AS (  
     SELECT DATEPART(DAY, [Date]), [Count] 
     FROM YourExampleTable 
     WHERE [Date] < DATEADD (day , -30 , getdate()) 
    ) 
SELECT DaysTable.name, DataTable.value 
FROM DaysTable LEFT JOIN 
     DataTable ON DaysTable.name = DataTable.name 
ORDER BY DaysTable.name 
9

Usando CTE:

WITH DateTable 
AS 
(
    SELECT CAST('20110101' AS Date) AS [DATE] 
    UNION ALL 
    SELECT DATEADD(dd, 1, [DATE]) 
    FROM DateTable 
    WHERE DATEADD(dd, 1, [DATE]) < cast('20110201' as Date) 
) 
SELECT dt.[DATE], ISNULL(md.[COUNT], 0) as [COUNT] 
FROM [DateTable] dt 
LEFT JOIN [MyData] md 
ON md.[DATE] = dt.[DATE] 

Esto es suponiendo que todo es una fecha; si es DateTime, tendrá que truncar (con DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE]))).

1

Sin Transact-SQL: MS SQL 2005 - Obtener una lista de todos los días de un mes:

En mi caso '20121201' es un valor predefinido.


SELECT TOp (Select Day(DateAdd(day, -Day(DateAdd(month, 1, 
'20121201')), 
          DateAdd(month, 1, '20121201')))) DayDate FROM (SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT 
NULL))-1,'20121201') as DayDate FROM sys.objects s1 CROSS JOIN 
sys.objects s2) q 
0

Para aquellos con una alergia a la recursividad

select SubQ.TheDate 
from 
(
    select DATEADD(day, a.a + (10 * b.a) + (100 * c.a), DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) - 30) AS TheDate 
    from 
    (
     (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a 
     cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b 
     cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c 
    ) 
    WHERE a.a + (10 * b.a) + (100 * c.a) < 30 
) AS SubQ 
ORDER BY TheDate 
0

intentarlo.

DECLARE @currentDate DATETIME = CONVERT(DATE, GetDate()) 
DECLARE @startDate DATETIME = DATEADD(DAY, -DAY(@currentDate)+1, @currentDate) 

;WITH fnDateNow(DayOfDate) AS 
(
    SELECT @startDate AS DayOfDate 
     UNION ALL 
    SELECT DayOfDate + 1 FROM fnDateNow WHERE DayOfDate < @currentDate 
) SELECT fnDateNow.DayOfDate FROM fnDateNow 
0

Mi escenario era un poco más complejo que el ejemplo OP, así que pensé en compartir para ayudar a otros que tienen problemas similares. Necesitaba agrupar las órdenes de venta por toma de fecha, mientras que las órdenes se almacenan con fecha y hora.

Así, en la tabla de búsqueda "día" realmente no podía almacenar como una fecha y hora con el momento '00: 00: 00.000' y obtener los partidos. Por lo tanto, almacené como una cadena e intenté unirme al valor convertido directamente.

Eso no devuelve ninguna fila cero, y la solución era hacer una sub-consulta devolver la fecha ya se convierte en una cadena.

Código de la muestra de la siguiente manera:

declare @startDate datetime = convert(datetime,'09/02/2016') 
declare @curDate datetime = @startDate 
declare @endDate datetime = convert(datetime,'09/09/2016') 
declare @dtFormat int = 102; 
DECLARE @null_Date varchar(24) = '1970-01-01 00:00:00.000' 

/* Initialize #days table */ 
select CONVERT(VARCHAR(24),@curDate, @dtFormat) as [Period] into #days 

/* Populate dates into #days table */ 
while (@curDate < @endDate) 
begin 
    set @curDate = dateadd(d, 1, @curDate) 
    insert into #days values (CONVERT(VARCHAR(24),@curDate, @dtFormat)) 
end 

/* Outer aggregation query to group by order numbers */ 
select [Period], count(c)-case when sum(c)=0 then 1 else 0 end as [Orders], 
sum(c) as [Lines] from 
(
    /* Inner aggregation query to sum by order lines */ 
    select 
     [Period], sol.t_orno, count(*)-1 as c 
     from (
      /* Inner query against source table with date converted */ 
      select convert(varchar(24),t_dldt, @dtFormat) as [shipdt], t_orno 
       from salesorderlines where t_dldt > @startDate 
     ) sol 
     right join #days on shipdt = #days.[Period]  
     group by [Period], sol.t_orno 
) as t 
group by Period 
order by Period desc 

drop table #days 

resultados de la muestra:

Period  Orders Lines 
2016.09.09 388  422 
2016.09.08 169  229 
2016.09.07 1  1 
2016.09.06 0  0 
2016.09.05 0  0 
2016.09.04 165  241 
2016.09.03 0  0 
2016.09.02 0  0 
1
respuesta de

@Alex K. es completamente correcto, pero no funciona para las versiones que no son compatibles recursiva expresiones de tabla comunes (como la versión con la que estoy trabajando). En este caso, lo siguiente haría el trabajo.

DECLARE @StartDate datetime = '2015-01-01' 
DECLARE @EndDate datetime = SYSDATETIME() 

;WITH days AS 
(
    SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, @StartDate), 0)) as d 
    FROM (SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1) 
      n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1 
      FROM sys.all_objects ORDER BY [object_id]) AS n 
) 
select days.d, count(t.val) 
    FROM days LEFT OUTER JOIN yourTable as t 
    ON t.dateColumn >= days.d AND t.dateColumn < DATEADD(DAY, 1, days.d) 
GROUP BY days.d 
ORDER BY days.d; 
0

DECLARE @StartDate DATE = '20110101', @NumberOfYears INT = 1; 
 

 
DECLARE @CutoffDate DATE = DATEADD(YEAR, @NumberOfYears, @StartDate); 
 

 

 
CREATE TABLE Calender 
 
(
 
    [date]  DATE 
 
); 
 

 

 
INSERT Calender([date]) 
 
SELECT d 
 
FROM 
 
(
 
    SELECT d = DATEADD(DAY, rn - 1, @StartDate) 
 
    FROM 
 
    (
 
    SELECT TOP (DATEDIFF(DAY, '2011-01-01', '2011-12-31')) 
 
     rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id]) 
 
    FROM sys.all_objects AS s1 
 
    CROSS JOIN sys.all_objects AS s2 
 
    ORDER BY s1.[object_id] 
 
) AS x 
 
) AS y; 
 

 

 
create table test(a date) 
 

 
insert into test values('1/1/2011') 
 
insert into test values('1/1/2011') 
 
insert into test values('1/1/2011') 
 
insert into test values('1/1/2011') 
 
insert into test values('1/1/2011') 
 

 
insert into test values('1/2/2011') 
 
insert into test values('1/2/2011') 
 
insert into test values('1/2/2011') 
 

 
insert into test values('1/4/2011') 
 
insert into test values('1/4/2011') 
 
insert into test values('1/4/2011') 
 
insert into test values('1/4/2011') 
 

 
select c.date as DATE,count(t.a) as COUNT from calender c left join test t on c.date = t.a group by c.date

Cuestiones relacionadas