Prova a trovare l'ultima volta che un valore è cambiato


26

Ho una tabella che ha un ID, un valore e una data. Ci sono molti ID, valori e date in questa tabella.

I record vengono inseriti periodicamente in questa tabella. L'ID rimarrà sempre lo stesso, ma a volte il valore cambierà.

Come posso scrivere una query che mi darà l'ID più l'ultima volta che il valore è cambiato? Nota: il valore aumenterà sempre.

Da questi dati di esempio:

  Create Table Taco
 (  Taco_ID int,
    Taco_value int,
    Taco_date datetime)

Insert INTO Taco 
Values (1, 1, '2012-07-01 00:00:01'),
        (1, 1, '2012-07-01 00:00:02'),
        (1, 1, '2012-07-01 00:00:03'),
        (1, 1, '2012-07-01 00:00:04'),
        (1, 2, '2012-07-01 00:00:05'),
        (1, 2, '2012-07-01 00:00:06'),
        (1, 2, '2012-07-01 00:00:07'),
        (1, 2, '2012-07-01 00:00:08')

Il risultato dovrebbe essere:

Taco_ID      Taco_date
1            2012-07-01 00:00:05

(Perché 00:05 è stata l'ultima volta che è stata Taco_Valuemodificata.)


2
Presumo taconon abbia nulla a che fare con il cibo?
Kermit,

5
Ho fame e vorrei mangiare dei tacos. Avevo solo bisogno di un nome per la tabella di esempio.
SqlSandwiches,

8
Hai scelto il tuo nome utente su una base simile?
Martin Smith,

1
Abbastanza possibile.
SqlSandwiches,

Risposte:


13

Queste due query si basano sul presupposto che Taco_valueaumenta sempre nel tempo.

;WITH x AS
(
  SELECT Taco_ID, Taco_date,
    dr = ROW_NUMBER() OVER (PARTITION BY Taco_ID, Taco_Value ORDER BY Taco_date),
    qr = ROW_NUMBER() OVER (PARTITION BY Taco_ID ORDER BY Taco_date)
  FROM dbo.Taco
), y AS
(
  SELECT Taco_ID, Taco_date,
    rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID, dr ORDER BY qr DESC)
  FROM x WHERE dr = 1
)
SELECT Taco_ID, Taco_date
FROM y 
WHERE rn = 1;

Un'alternativa con meno follia della funzione finestra:

;WITH x AS
(
  SELECT Taco_ID, Taco_value, Taco_date = MIN(Taco_date)
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
), y AS
(
  SELECT Taco_ID, Taco_date, 
    rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM x
)
SELECT Taco_ID, Taco_date FROM y WHERE rn = 1;

Esempi su SQLfiddle


Aggiornare

Per quelli che tenevano traccia, c'era contesa su ciò che accadrebbe se Taco_valuepotesse mai ripetere. Se potrebbe andare da 1 a 2 e poi di nuovo a 1 per un dato dato Taco_ID, le query non funzioneranno. Ecco una soluzione per quel caso, anche se non è proprio la tecnica delle lacune e delle isole che qualcuno come Itzik Ben-Gan potrebbe essere in grado di inventare, e anche se non è rilevante per lo scenario del PO - potrebbe essere rilevante per un futuro lettore. È un po 'più complesso e ho anche aggiunto una variabile aggiuntiva, una Taco_IDche ne ha solo una Taco_value.

Se vuoi includere la prima riga per qualsiasi ID in cui il valore non è cambiato affatto nell'intero set:

;WITH x AS
(
  SELECT *, rn = ROW_NUMBER() OVER 
    (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM dbo.Taco
), rest AS (SELECT * FROM x WHERE rn > 1)
SELECT  
  main.Taco_ID, 
  Taco_date = MIN(CASE 
    WHEN main.Taco_value = rest.Taco_value 
    THEN rest.Taco_date ELSE main.Taco_date 
  END)
FROM x AS main LEFT OUTER JOIN rest
ON main.Taco_ID = rest.Taco_ID AND rest.rn > 1
WHERE main.rn = 1
AND NOT EXISTS 
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND rn < rest.rn
   AND Taco_value <> rest.Taco_value
) 
GROUP BY main.Taco_ID;

Se vuoi escludere quelle righe, è un po 'più complesso, ma comunque modifiche minori:

;WITH x AS
(
  SELECT *, rn = ROW_NUMBER() OVER 
    (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM dbo.Taco
), rest AS (SELECT * FROM x WHERE rn > 1)
SELECT 
  main.Taco_ID, 
  Taco_date = MIN(
  CASE 
    WHEN main.Taco_value = rest.Taco_value 
    THEN rest.Taco_date ELSE main.Taco_date 
  END)
FROM x AS main INNER JOIN rest -- ***** change this to INNER JOIN *****
ON main.Taco_ID = rest.Taco_ID AND rest.rn > 1
WHERE main.rn = 1
AND NOT EXISTS
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND rn < rest.rn
   AND Taco_value <> rest.Taco_value
)
AND EXISTS -- ***** add this EXISTS clause ***** 
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND Taco_value <> rest.Taco_value
)
GROUP BY main.Taco_ID;

Esempi SQLfiddle aggiornati


Ho notato alcuni significativi problemi di prestazioni con OVER, ma l'ho usato solo poche volte e potrebbe non scriverlo bene. Hai notato qualcosa?
Kenneth Fisher,

1
@KennethFisher non specificamente con OVER. Come qualsiasi altra cosa, i costrutti di query dipendono fortemente dallo schema / dagli indici sottostanti per funzionare correttamente. Una clausola over che le partizioni subiranno gli stessi problemi di un GROUP BY.
Aaron Bertrand

@KennethFisher si prega di fare attenzione a non trarre conclusioni ampie e ampie da osservazioni singolari e isolate. Vedo gli stessi argomenti contro i CTE: "Beh, ho avuto questo CTE ricorsivo una volta e le sue prestazioni sono state risucchiate. Quindi non uso più i CTE."
Aaron Bertrand

Ecco perché ho chiesto. Non l'ho usato abbastanza per dire in un modo o nell'altro, ma le poche volte che l'ho usato sono stato in grado di ottenere prestazioni migliori con un CTE. Continuerò a giocarci comunque.
Kenneth Fisher,

@AaronBertrand Non credo che funzioneranno se valuericompare: Fiddle
ypercubeᵀᴹ

13

Fondamentalmente, questo è il suggerimento di @ Taryn "condensato" a un singolo SELECT senza tabelle derivate:

SELECT DISTINCT
  Taco_ID,
  Taco_date = MAX(MIN(Taco_date)) OVER (PARTITION BY Taco_ID)
FROM Taco
GROUP BY
  Taco_ID,
  Taco_value
;

Nota: questa soluzione tiene conto della clausola che Taco_valuepuò solo aumentare. (Più esattamente, presuppone cheTaco_value non possa tornare a un valore precedente - lo stesso della risposta collegata, in effetti.)

Una demo di SQL Fiddle per la query: http://sqlfiddle.com/#!3/91368/2


7
Whoa, nidificato MAX / MIN. MIND BLOWN +1
Aaron Bertrand

7

Dovresti essere in grado di utilizzare entrambe min()e le max()funzioni di aggregazione ottengono il risultato:

select t1.Taco_ID, MAX(t1.taco_date) Taco_Date
from taco t1
inner join
(
    select MIN(taco_date) taco_date,
        Taco_ID, Taco_value
    from Taco
    group by Taco_ID, Taco_value
) t2
    on t1.Taco_ID = t2.Taco_ID
    and t1.Taco_date = t2.taco_date
group by t1.Taco_Id

Vedi SQL Fiddle with Demo


5

Un'altra risposta che si basa sul presupposto che i valori non riappaiono (questa è sostanzialmente la query 2 di Aaron 2, condensata in un nido in meno):

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MIN(Taco_date) DESC),
    Taco_date = MIN(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT Taco_ID, Taco_value, Taco_date
FROM x 
WHERE Rn = 1 ;

Test su: SQL-Fiddle


E una risposta al problema più generale, in cui i valori possono riapparire:

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MAX(Taco_date) DESC),    
    Taco_date = MAX(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT t.Taco_ID, Taco_date = MIN(t.Taco_date)
FROM x
  JOIN dbo.Taco t
    ON  t.Taco_ID = x.Taco_ID
    AND t.Taco_date > x.Taco_date
WHERE x.Rn = 2 
GROUP BY t.Taco_ID ;

(o usando CROSS APPLYcosì valueviene mostrata tutta la riga relativa, incluso il ,):

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MAX(Taco_date) DESC),    
    Taco_date = MAX(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT t.*
FROM x
  CROSS APPLY 
  ( SELECT TOP (1) *
    FROM dbo.Taco t
    WHERE t.Taco_ID = x.Taco_ID
      AND t.Taco_date > x.Taco_date
    ORDER BY t.Taco_date
  ) t
WHERE x.Rn = 2 ;

Test su: SQL-Fiddle-2


I suggerimenti per il problema più generale non funzionano per gli ID che non presentano modifiche. Potrebbe essere corretto con l'aggiunta di voci fittizie al set originale (qualcosa del genere dbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date).
Andriy M,

@AndriyM Lo so. Ho ipotizzato che "modifica" significhi che vogliono risultati quando ci sono almeno 2 valori, l'OP non lo ha chiarito (e poiché era più facile scrivere :)
ypercubeᵀᴹ

2

FYI +1 per fornire struttura e dati di esempio. L'unica cosa che avrei potuto chiedere è l'output previsto per quei dati.

EDIT: Questo mi avrebbe fatto impazzire. Appena nuovo c'era un modo "semplice" per farlo. Mi sono sbarazzato delle soluzioni errate e ne ho messo uno che credo sia corretto. Ecco una soluzione simile a @bluefeets ma copre i test forniti da @AaronBertrand.

;WITH TacoMin AS (SELECT Taco_ID, Taco_value, MIN(Taco_date) InitialValueDate
                FROM Taco
                GROUP BY Taco_ID, Taco_value)
SELECT Taco_ID, MAX(InitialValueDate)
FROM TacoMin
GROUP BY Taco_ID

2
L'OP non chiede una data più recente, chiede quando le valuemodifiche.
ypercubeᵀᴹ

Ahhh, vedo il mio errore. Ho elaborato una risposta, ma è praticamente la stessa di @ Aaron, quindi non ha senso pubblicarla.
Kenneth Fisher,

1

Perché non ottenere semplicemente la differenza tra il valore di ritardo e il valore iniziale? se la differenza è zero non è cambiata, non è zero quindi è cambiata. Questo può essere fatto in una semplice query:

-- example gives the times the value changed in the last 24 hrs
SELECT
    LastUpdated, [DiffValue]
FROM (
  SELECT
      LastUpdated,
      a.AboveBurdenProbe1TempC - coalesce(lag(a.AboveBurdenProbe1TempC) over (order by ProcessHistoryId), 0) as [DiffValue]
  FROM BFProcessHistory a
  WHERE LastUpdated > getdate() - 1
) b
WHERE [DiffValue] <> 0
ORDER BY LastUpdated ASC

La lag...funzione analitica è stata introdotta solo "di recente" in SQL Server 2012. La domanda originale è chiedere una soluzione su SQL Server 2008 R2. La tua soluzione non funzionerebbe per SQL Server 2008 R2.
John aka hot2use,

-1

Questo potrebbe essere semplice come il seguente?

       SELECT taco_id, MAX(
             CASE 
                 WHEN taco_value <> MAX(taco_value) 
                 THEN taco_date 
                 ELSE null 
             END) AS last_change_date

Dato che taco_value aumenta sempre?

ps Io stesso sono abbastanza principiante SQL, tuttavia, imparando lentamente ma sicuramente.


1
Su SQL Server questo dà l'errore. Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Martin Smith,

2
Aggiungendo un punto al commento di Martin: sei al sicuro se pubblichi solo codice testato. Un modo semplice può essere andare su sqlfiddle.com se sei lontano dal tuo solito parco giochi.
dezso,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.