2015-12-10 8 views
6

Ho una tabella cronologia che acquisisce gli aggiornamenti di un determinato oggetto e, oltre ad altre informazioni, acquisisce l'ora in cui si è verificato questo aggiornamento. Quello che mi piacerebbe fare è SELECT il MIN(LogDate) corrispondente a una certa colonna ActionTaken.Selezione di un gruppo di date in SQL Server

Più in particolare, la tabella della cronologia può avere altri (più recente) righe in cui ActionTaken = 1, ma voglio cogliere la data ActionTakendivenne 1.

Esempio:

SELECT MIN(LogDate) AS FirstActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND FirstActionTaken = 1 

SELECT MIN(LogDate) AS SecondActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND SecondActionTaken = 1 

SELECT MIN(LogDate) AS ThirdActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND ThirdActionTaken = 1 

Questo funziona bene e ricevo le date corrette senza problemi. Dove sto correndo nei guai viene poi andare a select il MAX(LogDate) da questo gruppo:

SELECT MAX(LogDate) AS LastActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND LogDate IN 
    (
      ( SELECT MIN(LogDate) AS FirstActionDate 
       FROM HistoryTable 
       WHERE ID = 123 
        AND FirstActionTaken = 1 ), 

      ( SELECT MIN(LogDate) AS SecondActionDate 
       FROM HistoryTable 
       WHERE ID = 123 
        AND SecondActionTaken = 1 ), 

      ( SELECT MIN(LogDate) AS ThirdActionDate 
       FROM HistoryTable 
       WHERE ID = 123 
        AND ThirdActionTaken = 1 ) 
    ) 

funziona questo anche, ma io odio farlo in questo modo. Potrei salvare le dichiarazioni precedenti in variabili e solo da quelle; sarebbe sicuramente più leggibile, ma quale sarebbe la sintassi JOIN per questa query?

C'è un modo per combinare le prime tre dichiarazioni SELECT in una che restituisce tutte e tre le date e non è un pasticcio illeggibile?

Come posso afferrare il più recente LogDate (come una colonna separata) da questo set di risultati e senza la (apparentemente inutile) ripetendo SELECT affermazioni?

EDIT:

Qui ci sono alcuni link che ho trovato in relazione alle risposte che sono state date finora:

Ho questi ultimi aiuteranno gli altri a cercare soluzioni a problemi simili!

+0

utilizzare UNION e quindi è possibile utilizzare IN – JamieD77

risposta

2

EDIT 2

sulla base di nuove informazioni che possono essere raccolte da propria risposta del PO (su come definire l'ultima data azione), la query può essere ulteriormente semplificata semplicemente questo:

select coalesce(
     min(case when ThirdActionTaken = 1 then LogDate end), 
     min(case when SecondActionTaken = 1 then LogDate end), 
     min(case when FirstActionTaken = 1 then LogDate end) 
     ) as LastActionDate 
    from HistoryTable 
where id = 123 

UnPivot può essere usato anche:

select max(ActionDate) 
    from (select min(case when FirstActionTaken = 1 then LogDate end) as FirstActionDate, 
       min(case when SecondActionTaken = 1 then LogDate end) as SecondActionDate, 
       min(case when ThirdActionTaken = 1 then LogDate end) as ThirdActionDate 
      from HistoryTable 
      where id = 123) t 
unpivot (ActionDate for ActionDates in (FirstActionDate, SecondActionDate, ThirdActionDate)) unpvt 

EDIT: Breve spiegazione

Questa risposta è molto simile a quella a Gordon che utilizza l'aggregazione condizionale per ottenere le date di minimo 3 in una query.

Così la seguente parte della query:

select min(case when FirstActionTaken = 1 then LogDate end) as FirstActionDate, 
     min(case when SecondActionTaken = 1 then LogDate end) as SecondActionDate, 
     min(case when ThirdActionTaken = 1 then LogDate end) as ThirdActionDate 
    from HistoryTable 
where id = 123 

... potrebbe tornare qualcosa di simile ...

FirstActionDate SecondActionDate ThirdActionDate 
--------------- ---------------- --------------- 
    2015-01-01   2015-12-01   (null) 

Quindi, la clausola unpivot è ciò che "unpivots" le 3 colonne in un set di risultati con 3 righe ma una singola colonna invece:

ActionDate 
---------- 
2015-01-01 
2015-12-01 
    (null) 

Una volta che i risultati sono in questo formato, quindi una semplice funzione aggregata max (select max(ActionDate)) può essere applicata per ottenere il valore massimo delle 3 righe.

+0

Questo funziona benissimo, e si sente molto più bello del mio stesso tentativo :) Vi dispiacerebbe aggiungere qualche spiegazione su cosa sta succedendo qui? Soprattutto in relazione a 'UNPIVOT'. Grazie per la risposta! – levelonehuman

+0

Eccellente, grazie per l'aggiornamento sulla risposta - e scuse per non aver specificato che per cominciare! – levelonehuman

1

È possibile utilizzare un UNION per unire le 3 query per l'istruzione IN.

Qualcosa di simile

SELECT 
    MAX(ht1.LogDate) AS LastActionDate 
FROM 
    HistoryTable ht1 
WHERE 
    ht1.ID = 123 
    AND ht1.LogDate IN (SELECT 
         MIN(LogDate) AS FirstActionDate 
        FROM 
         HistoryTable ht2 
        WHERE 
         ht2.ID = ht1.ID 
         AND ht2.FirstActionTaken = 1 
        UNION 
        SELECT 
         MIN(LogDate) AS FirstActionDate 
        FROM 
         HistoryTable ht2 
        WHERE 
         ht2.ID = ht1.ID 
         AND ht2.SecondActionTaken = 1 
        UNION 
        SELECT 
         MIN(LogDate) AS FirstActionDate 
        FROM 
         HistoryTable ht2 
        WHERE 
         ht2.ID = ht1.ID 
         AND ht2.ThirdActionTaken = 1) 
3

Questo sarebbe più facile con una struttura di dati normalizzata. Ecco un metodo che utilizza l'aggregazione condizionale per calcolare le tre date minime.Poi prende il massimo di questi valori:

SELECT v.dt 
FROM (SELECT MIN(CASE WHEN FirstActionTaken = 1 THEN LogDate END) AS d1, 
      MIN(CASE WHEN SecondActionTaken = 1 THEN LogDate END) AS d2, 
      MIN(CASE WHEN ThirdActionTaken = 1 THEN LogDate END) AS d3  
    FROM HistoryTable 
    WHERE ID = 123 
    ) ht OUTER APPLY 
    (SELECT MAX(dt) as dt 
    FROM (VALUES (d1), (d2), (d3)) v(dt) 
    ) v; 
0

È possibile risolvere questo problema senza utilizzare PIVOT. Il seguente codice estende il codice iniziale per memorizzare i valori MIN in variabili e quindi calcola il valore massimo tra i quali:

DECLARE @FirstActionDate DATETIME = NULL; 
DECLARE @SecondActionDate DATETIME = NULL; 
DECLARE @ThirdActionDate DATETIME = NULL; 
DECLARE @LastActionDate DATETIME = NULL; 

SELECT @FirstActionDate = MIN(LogDate) 
FROM HistoryTable 
WHERE ID = 123 
    AND FirstActionTaken = 1 

SELECT @SecondActionDate = MIN(LogDate) 
FROM HistoryTable 
WHERE ID = 123 
    AND SecondActionTaken = 1 

SELECT @ThirdActionDate = MIN(LogDate) 
FROM HistoryTable 
WHERE ID = 123 
    AND ThirdActionTaken = 1 

-- calculate @LastActionDate as the greater from @FirstActionDate, @SecondActionDate and @ThirdActionDate. 
SET @LastActionDate = @FirstActionDate; 
IF (@SecondActionDate > @LastActionDate) SET @LastActionDate = @SecondActionDate; 
IF (@ThirdActionDate > @LastActionDate) SET @LastActionDate = @ThirdActionDate; 

SELECT @FirstActionDate AS [FirstActionDate] 
, @SecondActionDate  AS [SecondActionDate] 
, @ThirdActionDate  AS [ThirdActionDate] 
, @LastActionDate  AS [LastActionDate] 

Se si desidera che la data ultima azione assoluta, è possibile modificare il codice originale per solo una singola istruzione , come segue:

SELECT MAX(LogDate) AS [LastActionDate] 
, MIN(CASE WHEN FirstActionTaken = 1 THEN LogDate ELSE NULL END) AS [FirstActionDate] 
, MIN(CASE WHEN SecondActionTaken = 1 THEN LogDate ELSE NULL END) AS [SecondActionDate] 
, MIN(CASE WHEN ThirdActionTaken = 1 THEN LogDate ELSE NULL END) AS [ThirdActionDate] 
FROM HistoryTable 
WHERE ID = 123 
0

mio tentativo di refactoring del finale SELECT dichiarazione:

SELECT MIN(ht2.LogDate) AS FirstActionDate, 
     MIN(ht3.LogDate) AS SecondActionDate, 
     MIN(ht4.LogDate) AS ThirdActionDate, 
     COALESCE (
      MIN(ht4.LogDate), 
      MIN(ht3.LogDate), 
      MIN(ht2.LogDate) 
     ) AS LastActionDate 
FROM HistoryTable ht 
    INNER JOIN HistoryTable ht2 
     ON ht2.ID = ht.ID AND ht2.FirstActionTaken = 1 
    INNER JOIN HistoryTable ht3 
     ON ht3.ID = ht.ID AND ht3.SecondActionTaken = 1 
    INNER JOIN HistoryTable ht4 
     ON ht4.ID = ht.ID AND ht4.ThirdActionTaken = 1 
WHERE ht.ID = 123 
GROUP BY ht.ID 

Questo JOINS torna a HistoryTable per ogni colonna xActionTaken e SELECTS il MIN(LogDate) da ciascuno. Quindi, camminiamo all'indietro attraverso i risultati (ThirdAction, SecondAction, FirstAction) e restituiamo il primo che troviamo come LastActionTaken.

Ammettiamolo, questo è un po 'caotico, ma ho pensato che sarebbe stato utile mostrare un'altra alternativa per recuperare gli stessi dati.

anche degno di nota per le prestazioni:

Dopo aver eseguito la mia risposta contro i metodi UNPIVOT e OUTER APPLY, SSMS Execution Plan mostra che UNPIVOT e OUTER APPLY sono quasi uguali (tenendo circa.50% del tempo di esecuzione ciascuno).

Quando si confronta il mio metodo con uno di questi, il mio metodo richiede ca. 88% del tempo di esecuzione, dove UNPIVOT/OUTER APPLY prende solo 12% - così entrambi UNPIVOT e OUTER APPLY vengono eseguiti molto più velocemente (almeno in questo caso).

Il motivo per cui il mio metodo richiede molto più tempo è che SQL esegue una scansione tabella di HistoryTable per ogni volta che ci ritorno, per un totale di 4 scansioni. Con gli altri due metodi, questa azione viene eseguita solo una volta.

+0

La tua domanda non ha mai specificato che 'ThirdActionDate> SecondActionDate> FirstActionDate', ma la tua risposta implica che questo è sempre vero. Stando così le cose, non hai bisogno di alcun accenno, applicazione esterna o join. La query richiede solo l'aggregazione condizionale con una coalizione. Ho modificato la mia risposta. Questo dovrebbe darti la migliore prestazione. – sstan

Problemi correlati