Come posso selezionare le righe con il timestamp più recente per ogni valore chiave?


88

Ho una tabella dei dati del sensore. Ogni riga ha un ID sensore, un timestamp e altri campi. Voglio selezionare una singola riga con l'ultimo timestamp per ogni sensore, inclusi alcuni degli altri campi.

Ho pensato che la soluzione sarebbe stata raggruppare per ID sensore e quindi ordinare per max (timestamp) in questo modo:

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM sensorTable 
GROUP BY sensorID 
ORDER BY max(timestamp);

Questo mi dà un errore dicendo che "sensorField1 deve apparire nella clausola group by o essere utilizzato in un aggregato".

Qual è il modo corretto per affrontare questo problema?


1
Quale motore di DB stai usando?
juergen d

1
Mentre le risposte seguenti utilizzando JOIN sul valore Max (timestamp) dovrebbero funzionare, suggerirei di unirsi a SensorReadingId se ne hai uno su sensorTable.
Thomas Langston

Risposte:


94

Per completezza, ecco un'altra possibile soluzione:

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM sensorTable s1
WHERE timestamp = (SELECT MAX(timestamp) FROM sensorTable s2 WHERE s1.sensorID = s2.sensorID)
ORDER BY sensorID, timestamp;

Abbastanza autoesplicativo, penso, ma ecco altre informazioni se lo desideri, oltre ad altri esempi. Viene dal manuale di MySQL, ma la query sopra funziona con ogni RDBMS (implementando lo standard sql'92).


57

Questo può essere fatto in un modo relativamente elegante usando SELECT DISTINCT, come segue:

SELECT DISTINCT ON (sensorID)
sensorID, timestamp, sensorField1, sensorField2 
FROM sensorTable
ORDER BY sensorID, timestamp DESC;

Quanto sopra funziona per PostgreSQL (qualche informazione in più qui ) ma penso anche per altri motori. Nel caso non sia ovvio, ciò che fa è ordinare la tabella in base all'ID del sensore e al timestamp (dal più recente al meno recente), quindi restituisce la prima riga (ovvero il timestamp più recente) per ogni ID sensore univoco.

Nel mio caso d'uso ho ~ 10M di letture da ~ 1K sensori, quindi provare a unire il tavolo con se stesso su un filtro basato sul timestamp richiede molte risorse; quanto sopra richiede un paio di secondi.


Questa soluzione è davvero veloce.
Ena

Veloce e facile da capire. Grazie per aver spiegato anche il caso d'uso, poiché il mio è abbastanza simile.
Stef Verdonk

1
Sfortunatamente, questo non funziona per MySQL ( link )
silentsurfer

21

Puoi unirti al tavolo con se stesso (su ID sensore) e aggiungere left.timestamp < right.timestampcome condizione di unione. Poi si sceglie le righe, in cui right.idè null. Voilà, hai l'ultima voce per sensore.

http://sqlfiddle.com/#!9/45147/37

SELECT L.* FROM sensorTable L
LEFT JOIN sensorTable R ON
L.sensorID = R.sensorID AND
L.timestamp < R.timestamp
WHERE isnull (R.sensorID)

Ma tieni presente che questo richiederà molte risorse se hai una piccola quantità di ID e molti valori! Quindi, non lo consiglierei per una sorta di misurazione, in cui ogni sensore raccoglie un valore ogni minuto. Tuttavia, in un caso d'uso, in cui è necessario tenere traccia delle "revisioni" di qualcosa che cambia solo "a volte", è facile.


Questo è più veloce di altre risposte, almeno nel mio caso.
pioggia_

@rain_ Dipende davvero dal caso d'uso. Pertanto, non esiste una "risposta universale" a questa domanda.
dognose

19

È possibile selezionare solo colonne che si trovano nel gruppo o utilizzate in una funzione di aggregazione. Puoi usare un join per farlo funzionare

select s1.* 
from sensorTable s1
inner join 
(
  SELECT sensorID, max(timestamp) as mts
  FROM sensorTable 
  GROUP BY sensorID 
) s2 on s2.sensorID = s1.sensorID and s1.timestamp = s2.mts

... o select * from sensorTable where (sensorID, timestamp) in (select sensorID, max(timestamp) from sensorTable group by sensorID).
Arjan

Penso che anche "LEFT JOIN" sia applicato, non solo "INNER JOIN"; e una parte "e s1.timestamp = s2.mts" non è necessaria IMHO. Eppure, consiglio di creare un indice su due campi: sensorID + timestamp - la velocità delle query aumenta alla grande!
Igor

4
WITH SensorTimes As (
   SELECT sensorID, MAX(timestamp) "LastReading"
   FROM sensorTable
   GROUP BY sensorID
)
SELECT s.sensorID,s.timestamp,s.sensorField1,s.sensorField2 
FROM sensorTable s
INNER JOIN SensorTimes t on s.sensorID = t.sensorID and s.timestamp = t.LastReading

2

C'è una risposta comune che non ho ancora visto qui, che è la funzione Window. È un'alternativa alla sottoquery correlata, se il tuo DB lo supporta.

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM (
    SELECT sensorID,timestamp,sensorField1,sensorField2
        , ROW_NUMBER() OVER(
            PARTITION BY sensorID
            ORDER BY timestamp
        ) AS rn
    FROM sensorTable s1
WHERE rn = 1
ORDER BY sensorID, timestamp;

Lo uso più che le sottoquery correlate. Sentiti libero di prendermi in giro nei commenti sull'efficacia, non sono troppo sicuro di come si accumuli a questo riguardo.


0

Ho avuto per lo più lo stesso problema e ho trovato una soluzione diversa che rende questo tipo di problema banale da interrogare.

Ho una tabella dei dati dei sensori (dati di 1 minuto da circa 30 sensori)

SensorReadings->(timestamp,value,idSensor)

e ho una tabella dei sensori che contiene molte cose per lo più statiche sul sensore, ma i campi rilevanti sono questi:

Sensors->(idSensor,Description,tvLastUpdate,tvLastValue,...)

TvLastupdate e tvLastValue sono impostati in un trigger sugli inserimenti nella tabella SensorReadings. Ho sempre accesso diretto a questi valori senza dover fare costose query. Questo denormalizza leggermente. La domanda è banale:

SELECT idSensor,Description,tvLastUpdate,tvLastValue 
FROM Sensors

Uso questo metodo per i dati che vengono interrogati spesso. Nel mio caso ho una tabella dei sensori e una tabella degli eventi di grandi dimensioni, che contengono dati in arrivo a livello di minuto E dozzine di macchine aggiornano dashboard e grafici con quei dati. Con il mio scenario di dati il ​​metodo trigger e cache funziona bene.

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.