2010-08-31 39 views
6

Tengo una tabla de datos que se ve un poco como esto:Iterar a través fechas en SQL

Name StartTime    FinishTime    Work 
Bob  2010-08-03 08:00:00 2010-08-03 12:00:00  4 
Bob  2010-08-03 13:00:00 2010-08-03 16:00:00  3 
Pete 2010-08-04 08:00:00 2010-08-04 12:00:00  4 
Mark 2010-08-04 10:00:00 2010-08-04 12:00:00  2 

Ninguno de estos intervalos de tiempo cada vez debe abarcar más de la medianoche.
Quiero escribir SQL que me dará la siguiente salida, dada una entrada Fecha de Inicio de 2010-08-02 y un acabado Fecha de 2010-08-05

Date   Name TotalWork 
2010-08-03 Bob 7 
2010-08-03 Pete 3 
2010-08-04 Pete 4 
2010-08-04 Mark 2 

que podría vivir con, y en hecho en última instancia puede necesitar, para tener cualquier día que no tienen trabajo asociado también estar representados en el conjunto de resultados, tal vez como una fila de la siguiente manera:

2010-08-05  NULL 0 

no estoy muy seguro de la manera de recorrer las fechas en SQL de la misma manera que lo haría con otros idiomas.

Para dar un poco de contexto, la salida de esto finalmente se conectará a un control Stacked Chart .Net.

¿Podría alguien darme una pista, un enlace a un tutorial u otra ayuda? De lo contrario, creo que voy a estar jugando con esto por días!

¡Gracias!

Jonathan

Respuesta

7

Prueba esto:

Select DateAdd(day, 0, DateDiff(day, 0, StartDate)) Date, 
    Name, Sum (Work) TotalWork 
From TableData 
Group By Name, DateAdd(day, 0, DateDiff(day, 0, StartDate)) 

para obtener los días que faltan es más difícil.

Declare @SD DateTime, @ED DateTime -- StartDate and EndDate variables 
    Select @SD = DateAdd(day, 0, DateDiff(day, 0, Min(StartDate))), 
      @ED = DateAdd(day, 0, DateDiff(day, 0, Max(StartDate))) 
    From TableData 
    Declare @Ds Table (aDate SmallDateTime) 
    While @SD <= @ED Begin 
     Insert @Ds(aDate) Values @SD 
     Set @SD = @SD + 1 
    End 
-- ---------------------------------------------------- 
Select DateAdd(day, 0, DateDiff(day, 0, td.StartDate)) Date, 
    td.Name, Sum (td.Work) TotalWork 
From @Ds ds Left Join TableData td 
    On DateAdd(day, 0, DateDiff(day, 0, tD.StartDate)) = ds.aDate 
Group By Name, DateAdd(day, 0, DateDiff(day, 0, tD.StartDate)) 

EDIT, estoy volviendo a visitar esto con una solución que utiliza una expresión de tabla común (CTE). Esto NO requiere el uso de una tabla de fechas.

Declare @SD DateTime, @ED DateTime 
    Declare @count integer = datediff(day, @SD, @ED) 
    With Ints(i) As 
     (Select 0 Union All 
    Select i + 1 From Ints 
    Where i < @count) 
    Select DateAdd(day, 0, DateDiff(day, 0, td.StartDate)) Date, 
     td.Name, Sum (td.Work) TotalWork 
    From Ints i 
     Left Join TableData d 
      On DateDiff(day, @SD, d.StartDate) = i.i 
    Group By d.Name, DateAdd(day, 0, DateDiff(day, 0, d.StartDate)) 
+0

Ah ... eso se ve muy bien, gracias. Supongo que si quiero usar los parámetros @SD y @ED, independientemente de si quiero los días faltantes, ¿entonces tengo que construir la tabla temporal? – JonRed

+0

@JonRed, No, si no desea las fechas que faltan, utilice el primer SQL. En ese caso, no necesita las variables T-Sql ni la tabla temporal. Si necesita las fechas faltantes, necesita la tabla temporal y las variables T-Sql son necesarias para completarla. –

+0

Truco realmente bueno ... – Zafer

5

La forma en que itera a través de las filas en SQL es que no lo hace. SQL es un lenguaje basado en conjuntos que requiere una mentalidad completamente diferente de otros lenguajes de procedimiento. Si vas a trabajar con SQL, realmente necesitas hacer ese cambio de pensamiento para tener éxito.

Así es como me gustaría manejar esto:

SELECT 
    CONVERT(VARCHAR(10), StartTime, 121) AS [date], 
    name, 
    SUM(work) 
FROM 
    My_Table 
WHERE 
    StartTime >= @start_date AND 
    StartTime < DATEADD(dy, 1, @finish_date) 
GROUP BY 
    CONVERT(VARCHAR(10), StartTime, 121), 
    name 

Además, el diseño de la tabla parece que viola los estándares normales de diseño de base de datos. Su columna de "trabajo" es realmente solo un cálculo entre el tiempo de inicio y el tiempo de finalización. Eso lo convierte en una duplicación de los mismos datos, lo que puede causar todo tipo de problemas. Por ejemplo, ¿qué haces cuando tu StartTime y FinishTime tienen 4 horas de diferencia, pero el "Trabajo" dice 5 horas?

Para incluir las fechas sin trabajo asociado, deberá manejarlas en la interfaz, o necesitará una tabla de "Calendario". Tendría todas las fechas y harías una unión IZQUIERDA a eso con tu mesa. Por ejemplo:

SELECT 
    CONVERT(VARCHAR(10), C.StartTime, 121) AS [date], 
    MT.name, 
    SUM(MT.work) 
FROM 
    Calendar C 
LEFT JOIN My_Table MT ON 
    MT.StartDate BETWEEN C.StartTime and C.FinishTime 
WHERE 
    C.StartTime >= @start_date AND 
    C.StartTime < DATEADD(dy, 1, @finish_date) 
GROUP BY 
    CONVERT(VARCHAR(10), C.StartTime, 121), 
    MT.name 

La tabla de calendario también le permite añadir información adicional a las fechas, como una bandera para las vacaciones, "tiempo extra" días (tal vez se cuenta como tiempo y medio trabajan los domingos), etc.

NOTA: La solución de Charles Bretana es probablemente un poco más limpia ya que mantiene los tipos de datos como fechas en lugar de convertirlos en cadenas. Aunque voy a dejar esto aquí para algunos de los otros comentarios.

+0

¿Quiso decir 'Convert'? –

+0

Desafortunadamente, el trabajo no es solo la diferencia entre dos fechas; hay un elemento% de esfuerzo que no está registrado en esta tabla. En aras de la simplicidad, he representado varias tablas vinculadas como una sola tabla. Gracias por esto sin embargo. Mientras estoy trabajando en los problemas, mi diseño está cambiando y sus comentarios son muy útiles. – JonRed

+1

@Charles - No, COVERT es una nueva función estándar de ANSI para cuando necesita columnas de alto secreto. ;) Gracias por la corrección. –