2013-07-03 10 views
5

Ho un 2008 tabella di database MS SQL che è simile al seguente:Come raggruppare sequenziale, timestamped righe in SQL e restituire l'intervallo di date per ogni gruppo

Registration | Date | DriverID | TrailerID

Un esempio di ciò che alcuni dei dati sarà simile è la seguente:

AB53EDH,2013/07/03 10:00,54,23 
AB53EDH,2013/07/03 10:01,54,23 
... 
AB53EDH,2013/07/03 10:45,54,23 
AB53EDH,2013/07/03 10:46,54,NULL <-- Trailer changed 
AB53EDH,2013/07/03 10:47,54,NULL 
... 
AB53EDH,2013/07/03 11:05,54,NULL 
AB53EDH,2013/07/03 11:06,54,102 <-- Trailer changed 
AB53EDH,2013/07/03 11:07,54,102 
... 
AB53EDH,2013/07/03 12:32,54,102 
AB53EDH,2013/07/03 12:33,72,102 <-- Driver changed 
AB53EDH,2013/07/03 12:34,72,102 

come si può vedere, i dati rappresentano il driver, e che del rimorchio sono state allegate a cui registrazione in qualsiasi punto nel tempo. Quello che mi piacerebbe fare è generare un report che contenga periodi per i quali sono attive tutte le combinazioni di driver e trailer. Così, per i dati di esempio di cui sopra, vorrei generare qualcosa che assomiglia a questo:

Registration,StartDate,EndDate,DriverID,TrailerID 
AB53EDH,2013/07/03 10:00,2013/07/03 10:45,54,23 
AB53EDH,2013/07/03 10:46,2013/07/03 11:05,54,NULL 
AB53EDH,2013/07/03 11:06,2013/07/03 12:32,54,102 
AB53EDH,2013/07/03 12:33,2013/07/03 12:34,72,102 

Come si va a fare questo tramite SQL?

AGGIORNAMENTO: Grazie alle risposte finora. Sfortunatamente, hanno smesso di funzionare quando l'ho applicato ai dati di produzione che ho. Le query inviate fino a quel momento non funzionano correttamente se applicate su parte dei dati.

Ecco alcune query di esempio per generare una tabella di dati e popolarla con i dati fittizi sopra. Ci sono più dati qui che nell'esempio sopra: il driver, le combinazioni di trailer 54,23 e 54, NULL sono state ripetute per assicurarsi che le query riconoscano che si tratta di due gruppi distinti. Ho anche replicato gli stessi dati tre volte con diversi intervalli di date, al fine di verificare se le query funziona quando viene eseguito su una parte del set di dati:

CREATE TABLE [dbo].[TempTable](
    [Registration] [nvarchar](50) NOT NULL, 
    [Date] [datetime] NOT NULL, 
    [DriverID] [int] NULL, 
    [TrailerID] [int] NULL 
) 

INSERT INTO dbo.TempTable 
VALUES 
('AB53EDH','2013/07/03 10:00', 54,23), 
('AB53EDH','2013/07/03 10:01', 54,23), 
('AB53EDH','2013/07/03 10:45', 54,23), 
('AB53EDH','2013/07/03 10:46', 54,NULL), 
('AB53EDH','2013/07/03 10:47', 54,NULL), 
('AB53EDH','2013/07/03 11:05', 54,NULL), 
('AB53EDH','2013/07/03 11:06', 54,102), 
('AB53EDH','2013/07/03 11:07', 54,102), 
('AB53EDH','2013/07/03 12:32', 54,102), 
('AB53EDH','2013/07/03 12:33', 72,102), 
('AB53EDH','2013/07/03 12:34', 72,102), 
('AB53EDH','2013/07/03 13:00', 54,102), 
('AB53EDH','2013/07/03 13:01', 54,102), 
('AB53EDH','2013/07/03 13:02', 54,102), 
('AB53EDH','2013/07/03 13:03', 54,102), 
('AB53EDH','2013/07/03 13:04', 54,23), 
('AB53EDH','2013/07/03 13:05', 54,23), 
('AB53EDH','2013/07/03 13:06', 54,23), 
('AB53EDH','2013/07/03 13:07', 54,NULL), 
('AB53EDH','2013/07/03 13:08', 54,NULL), 
('AB53EDH','2013/07/03 13:09', 54,NULL), 
('AB53EDH','2013/07/03 13:10', 54,NULL), 
('AB53EDH','2013/07/03 13:11', NULL,NULL) 

INSERT INTO dbo.TempTable 
SELECT Registration, DATEADD(M, -1, Date), DriverID, TrailerID 
FROM dbo.TempTable 
WHERE Date > '2013/07/01' 

INSERT INTO dbo.TempTable 
SELECT Registration, DATEADD(M, 1, Date), DriverID, TrailerID 
FROM dbo.TempTable 
WHERE Date > '2013/07/01' 
+0

Penso che ci sia un errore dati sui risultati attesi: 'AB53EDH, 2013/07/03 10: 06,2013/07/03 12: 32,54,102' potrebbe essere' AB53EDH, 2013/07/03 1 ** 1 **: 06,2013/07/03 12: 32,54,102' – armen

+0

+1. . . Hai un codice funzionante nella tua domanda. Un'ispirazione per lavorarci. –

+1

@armen: Grazie - corretto –

risposta

3

Questa query utilizza CTE a:

  1. Creare un insieme ordinato di record raggruppati per registrazione
  2. Per ogni record, catturano i dati del record precedente
  3. confrontare i dati attuali e precedenti per determinare se il record corrente è una nuova istanza di un assegnazione di driver/trailer
  4. Ricevi solo i nuovi record
  5. Per ogni nuovo record, ottenere l'ultima data prima di un nuovo driver/rimorchio assegnazione avviene

Link SQL Fiddle

codice qui sotto:

;WITH c AS (
-- Group records by Registration, assign row numbers in order of date 
SELECT 
    ROW_NUMBER() OVER (
    PARTITION BY Registration 
    ORDER BY Registration, [Date]) 
    AS Rn, 
    Registration, 
    [Date], 
    DriverID, 
    TrailerID 
FROM 
    TempTable 
) 
,c2 AS (
-- Self join to table to get Driver and Trailer from previous record 
SELECT 
    t1.Rn, 
    t1.Registration, 
    t1.[Date], 
    t1.DriverID, 
    t1.TrailerID, 
    t2.DriverID AS PrevDriverID, 
    t2.TrailerID AS PrevTrailerID 
FROM 
    c t1 
LEFT OUTER JOIN 
    c t2 
ON 
    t1.Registration = t2.Registration 
AND 
    t2.Rn = t1.Rn - 1 
) 
,c3 AS (
-- Use INTERSECT to determine if this record is new in sequence 
SELECT 
    Rn, 
    Registration, 
    [Date], 
    DriverID, 
    TrailerID, 
    CASE WHEN NOT EXISTS (
      SELECT DriverID, TrailerID 
      INTERSECT 
      SELECT PrevDriverID, PrevTrailerID) 
     THEN 1 
     ELSE 0 
    END AS IsNew 
FROM c2 
) 
-- For all new records in sequence, 
-- get the last date logged before a new record appeared 
SELECT 
    Registration, 
    [Date] AS StartDate, 
    COALESCE (
    (
     SELECT TOP 1 [Date] 
     FROM c3 
     WHERE Registration = t.Registration 
     AND Rn < (
     SELECT TOP 1 Rn 
     FROM c3 
     WHERE Registration = t.Registration 
     AND Rn > t.Rn 
     AND IsNew = 1 
     ORDER BY Rn) 
     ORDER BY Rn DESC 
    ) 
    , [Date]) AS EndDate, 
    DriverID, 
    TrailerID 
FROM 
    c3 t 
WHERE 
    IsNew = 1 
ORDER BY 
    Registration, 
    StartDate 
1

try-:

DECLARE @TempTable AS TABLE (
    [Registration] [nvarchar](50) NOT NULL, 
    [Date] [datetime] NOT NULL, 
    [DriverID] [int] NULL, 
    [TrailerID] [int] NULL 
) 

INSERT INTO @TempTable 
VALUES 
('AB53EDH','2013-07-03 10:00', 54,23), 
('AB53EDH','2013-07-03 10:01', 54,23), 
('AB53EDH','2013-07-03 10:45', 54,23), 
('AB53EDH','2013-07-03 10:46', 54,nULL), 
('AB53EDH','2013-07-03 10:47', 54,NULL), 
('AB53EDH','2013-07-03 11:05', 54,NULL), 
('AB53EDH','2013-07-03 11:06', 54,102), 
('AB53EDH','2013-07-03 11:07', 54,102), 
('AB53EDH','2013-07-03 12:32', 54,102), 
('AB53EDH','2013-07-03 12:33', 72,102), 
('AB53EDH','2013-07-03 12:34', 72,102) 

SELECT t1.Registration, MIN(t1.Date) AS StartDate, MAX(t1.date) AS EndDate, t1.DriverID, t1.TrailerID 
FROM @TempTable AS t1 
INNER JOIN @TempTable AS t2 
    ON t1.Registration = t2.Registration AND (t1.DriverID = t2.DriverID OR t1.TrailerID = t2.TrailerID) 
GROUP BY t1.Registration, t1.DriverID, t1.TrailerID 
    ORDER BY MIN(t1.Date) 
+0

Sfortunatamente, questo dà i risultati sbagliati. –

+1

Funziona ora, grazie! –

+0

Sfortunatamente, ci sono ancora problemi con questo metodo. Se la stessa combinazione di autista e trailer viene visualizzata più avanti nel tempo, la query li considera come un gruppo anziché riconoscere che si tratta di due gruppi distinti separati da un periodo di tempo. Ho aggiornato la domanda originale per includere altri punti dati per illustrare questo aspetto. Noterai che la combinazione di 54,23 e 54, NULL per Driver, Trailer viene ripetuta due volte, ma i risultati della tua query non lo riflettono. –

1

Qui è un approccio che utilizza subquery correlate:

with tt as (
     select tt.*, 
       (select top 1 date 
       from TempTable tt2 
       where tt2.Registration = tt.Registration and 
        tt2.DriverID = tt.DriverID and 
        (tt2.TrailerID = tt.TrailerID or tt2.TrailerID is null and tt.TrailerID is null) and 
        tt2.Date < tt.Date 
       order by date desc 
      ) prevDate 
     from TempTable tt 
    ) 
select registration, min(date) as startdate, max(date) as enddate, driverid, trailerid 
from (select tt.*, 
      (select top 1 date 
       from tt tt3 
       where prevDate is NULL and 
        tt3.Date <= tt.date 
       order by Date desc 
      ) as grp 
     from TempTable tt 
    ) tt 
group by grp, Registration, DriverID, trailerid; 

il CTE sta facendo un lag(date) sulla registrazione, DriverID, e trailerid, producendo la data precedente per un record. Questo è NULL all'inizio di una sequenza di record.

La sottoquery trova la data più recente su un record NULL su o prima di un determinato record. Questo agisce come una variabile di raggruppamento. Tutto in una sequenza ha lo stesso grp a questo punto.

La query finale lo aggrega nel formato desiderato.

Questa è una query complicata. La sintassi può essere leggermente semplificata in SQL Server 2012, utilizzando le funzioni di aggregazione lag() e cumulative. Con queste funzioni, puoi seguire essenzialmente lo stesso approccio.

EDIT:

Ouch. La query precedente ha un errore logico nel calcolo della data precedente. La correzione richiede che le date siano univoche nei dati.

L'errore di cui sopra è che cerca la data precedente in cui la tripla di colonne corrisponde. Sciocco, stupido, stupido. Perché ci può essere una tripla che corrisponde ma prima nei dati. Invece, è necessario ottenere la data precedente e quindi vedere se le triple corrispondenze.

Quanto segue implementa questo con un join aggiuntivo. È in esecuzione here su SQL Fiddle.

with tt as (
     select tt.*, tt3.date as PrevDate 
     from (select tt.*, 
        (select top 1 date 
        from TempTable tt2 
        where tt2.date < tt.date 
        order by date desc 
        ) prevDate1 
      from TempTable tt 
      ) tt left outer join 
      TempTable tt3 
      on tt.prevdate1 = tt3.date and 
       tt3.Registration = tt.Registration and 
       tt3.DriverID = tt.DriverID and 
       (tt3.TrailerID = tt.TrailerID or tt3.TrailerID is null and tt.TrailerID is null) 
    ) 
select registration, count(*), min(date) as startdate, max(date) as enddate, driverid, trailerid 
from (select tt.*, 
      (select top 1 date 
       from tt tt3 
       where prevDate is NULL and 
        tt3.Date <= tt.date 
       order by Date desc 
      ) as grp 
     from TempTable tt 
    ) tt 
group by grp, Registration, DriverID, trailerid; 
+0

Prima di tutto, grazie per aver trovato il tempo di rispondere alla mia domanda. Poiché ho studiato la query e ho provato ad applicarla ai miei dati di produzione, mi sono reso conto che sfortunatamente non funziona in tutti i casi. Sembra che la query si basi su ogni combinazione di driver/trailer preceduta da un'altra combo (anche se diversa) che ha il suo precedente impostato su NULL (cioè questa è la prima occorrenza di quella combinazione nei dati). Sfortunatamente, nei dati di produzione, questo non è il caso in quanto tutte le combinazioni possono verificarsi più volte nello stesso intervallo di dati. –

+0

@AmrBekhit. . . Non capisco il tuo commento Questa query cattura più sequenze di righe, in base alla data, che hanno lo stesso 'registration',' driverId' e 'trailerId'.La stessa tripla può apparire più volte, con date diverse, e appariranno corrispondentemente più volte nei dati. Il calcolo di 'prevDate' determina dove inizia ogni sequenza. –

+0

Ho modificato la query per lavorare su un sottoinsieme dei dati nella tabella (ho incollato la query qui: http://pastebin.com/9E6a3J00). Prova a ricreare TempTable usando il codice aggiornato nella domanda e con la nuova tabella, quindi esegui la query. Noterai che per i gruppi che cadono il 2013/08/03 i raggruppamenti non sono corretti. –

Problemi correlati