2010-08-31 23 views
6

Ho una tabella di dati che sembra un po 'come questo:Iterare date in 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 

Nessuno di questi intervalli di date deve mai estendersi oltre la mezzanotte.
Voglio scrivere SQL che mi darà il risultato seguente, dato un input Data di inizio del 2010-08-02 e una finitura Data di 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 

ho potuto vivere con, e in infatti può in ultima analisi essere necessario, per avere giorni che non hanno lavoro associato anche essere rappresentati nei risultati esposti, forse come una riga come questa:

2010-08-05  NULL 0 

io non sono molto sicuro come eseguire un'iterazione in date in SQL allo stesso modo che vorrei con altre lingue.

Per dare un po 'di contesto, l'output di questo verrà inserito in un controllo .Net.

Qualcuno potrebbe darmi un indizio, un collegamento a un tutorial o qualche altro aiuto? Altrimenti, penso che ci metterò a giocherellare per giorni!

Grazie!

Jonathan

risposta

7

Prova questo:

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

per ottenere i giorni mancanti è più difficile.

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, sto rivisitando questo con una soluzione che utilizza una Common Table Expression (CTE). Questo NON richiede l'uso di una tabella di date.

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 ... sembra davvero buono, grazie. Lo prendo se voglio usare i parametri @SD e @ED, indipendentemente dal fatto che voglio i giorni mancanti, quindi devo costruire la tabella temporanea? – JonRed

+0

@JonRed, No se non vuoi le date mancanti, quindi usa il primo SQL. In quel caso non hai bisogno delle variabili T-Sql o della tabella temporanea. Se hai bisogno delle date mancanti, allora hai bisogno della tabella temporanea e le variabili T-Sql sono necessarie per popolarlo. –

+0

Davvero un buon trucco ... – Zafer

5

Il modo in cui si scorre le righe in SQL è che non è così. SQL è un linguaggio basato su set che richiede una mentalità completamente diversa da altri linguaggi procedurali. Se lavorerai con SQL hai davvero bisogno di essere in grado di fare quel cambiamento nel pensare di avere successo.

Ecco come avrei gestire questo:

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 

Inoltre, il vostro disegno tavolo sembra viola normali standard di progettazione di database. La colonna "lavoro" è in realtà solo un calcolo tra StartTime e FinishTime. Ciò lo rende una duplicazione degli stessi dati, che può causare tutti i tipi di problemi. Ad esempio, cosa fai quando StartTime e FinishTime sono a 4 ore di distanza, ma il "Lavoro" dice 5 ore?

Per includere le date senza lavoro associato, è necessario gestirle nel front-end, oppure è necessaria una tabella "Calendario". Avrebbe tutte le date in esso e tu avresti fatto un SINISTRA SINISTRA con quella con il tuo tavolo. Per esempio:

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 tabella del calendario permette anche di aggiungere ulteriori informazioni alle date, come una bandiera per le vacanze, "straordinari" giorni (forse lavorano conta come tempo e mezzo la domenica), etc.

NOTA: La soluzione di Charles Bretana è probabilmente un po 'più pulita poiché mantiene i tipi di dati come datari invece di trasformarli in stringhe. Lascerò questo qui per alcuni degli altri commenti.

+0

Intendevi 'Converti'? –

+0

Il lavoro non è solo la differenza tra due date sfortunatamente; c'è un elemento% effort che non è registrato in questa tabella. Per semplicità ho rappresentato diverse tabelle collegate come una tabella. Grazie per questo però. Mentre sto affrontando i problemi il mio design si sta spostando e i tuoi commenti sono molto utili. – JonRed

+1

@Charles - No, COVERT è una nuova funzione ANSI standard per quando sono necessarie colonne top secret. ;) Grazie per la correzione. –