¿Cómo puedo seleccionar filas con la marca de tiempo más reciente para cada valor clave?

86

Tengo una tabla de datos de sensores. Cada fila tiene una identificación de sensor, una marca de tiempo y otros campos. Quiero seleccionar una sola fila con la última marca de tiempo para cada sensor, incluidos algunos de los otros campos.

Pensé que la solución sería agrupar por ID de sensor y luego ordenar por max (marca de tiempo) así:

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

Esto me da un error que dice que "sensorField1 debe aparecer en la cláusula group by o usarse en un agregado".

¿Cuál es la forma correcta de abordar este problema?

francamente
fuente
1
¿Qué motor de base de datos estás usando?
juergen d
1
Si bien las respuestas a continuación que usan JOINs en el valor Max (marca de tiempo) deberían funcionar, sugeriría unirse a un SensorReadingId si tiene uno en el sensorTable.
Thomas Langston

Respuestas:

94

En aras de la integridad, aquí hay otra posible solución:

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;

Creo que es bastante autoexplicativo, pero aquí hay más información si lo desea, así como otros ejemplos. Es del manual de MySQL, pero la consulta anterior funciona con todos los RDBMS (implementando el estándar sql'92).

pantalones de lujo
fuente
56

Esto se puede hacer de una manera relativamente elegante usando SELECT DISTINCT, de la siguiente manera:

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

Lo anterior funciona para PostgreSQL (más información aquí ) pero creo que también para otros motores. En caso de que no sea obvio, lo que hace es ordenar la tabla por ID de sensor y marca de tiempo (más reciente a más antiguo), y luego devuelve la primera fila (es decir, la última marca de tiempo) para cada ID de sensor único.

En mi caso de uso, tengo ~ 10M lecturas de ~ 1K sensores, por lo que intentar unir la tabla consigo mismo en un filtro basado en marcas de tiempo requiere muchos recursos; lo anterior toma un par de segundos.

Svet
fuente
Esta solución es realmente rápida.
Ena
Rápido y fácil de entender. Gracias por explicar el caso de uso también, ya que el mío es bastante similar.
Stef Verdonk
Desafortunadamente, esto no funciona para MySQL ( enlace )
silentsurfer
21

Puede unirse a la tabla consigo mismo (en la identificación del sensor) y agregar left.timestamp < right.timestampcomo condición de unión. Luego escoges las filas, donde right.idestá null. Listo, tienes la última entrada por sensor.

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)

Pero tenga en cuenta que esto consumirá muchos recursos si tiene una pequeña cantidad de identificadores y muchos valores. Por lo tanto, no recomendaría esto para algún tipo de material de medición, donde cada sensor recopila un valor cada minuto. Sin embargo, en un caso de uso, donde necesita realizar un seguimiento de las "revisiones" de algo que cambia "a veces", es fácil.

perder
fuente
Esto es más rápido que otras respuestas, al menos en mi caso.
lluvia_
@rain_ Realmente depende del caso de uso. Por tanto, no existe una "respuesta universal" a esta pregunta.
reconocer el
19

Solo puede seleccionar columnas que están en el grupo o que se usan en una función agregada. Puede usar una combinación para que esto funcione

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
juergen d
fuente
... o select * from sensorTable where (sensorID, timestamp) in (select sensorID, max(timestamp) from sensorTable group by sensorID).
Arjan
Creo que "LEFT JOIN" también se aplica, no solo "INNER JOIN"; y una parte "y s1.timestamp = s2.mts" no es necesario en mi humilde opinión. Y, sin embargo, recomiendo crear un índice en dos campos: sensorID + marca de tiempo: ¡la velocidad de consulta aumenta genial!
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
Joel Coehoorn
fuente
2

Hay una respuesta común que todavía no he visto aquí, que es la función de ventana. Es una alternativa a la subconsulta correlacionada, si su base de datos la admite.

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;

En realidad, uso esto más que las subconsultas correlacionadas. Siéntase libre de criticarme en los comentarios sobre efeciance, no estoy muy seguro de cómo se compara en ese sentido.

Jamie Marshall
fuente
0

Tuve casi el mismo problema y terminé con una solución diferente que hace que este tipo de problema sea trivial de consultar.

Tengo una tabla de datos de sensores (datos de 1 minuto de unos 30 sensores)

SensorReadings->(timestamp,value,idSensor)

y tengo una tabla de sensores que tiene muchas cosas principalmente estáticas sobre el sensor, pero los campos relevantes son estos:

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

TvLastupdate y tvLastValue se establecen en un disparador en inserciones en la tabla SensorReadings. Siempre tengo acceso directo a estos valores sin necesidad de realizar consultas costosas. Esto se desnormaliza ligeramente. La consulta es trivial:

SELECT idSensor,Description,tvLastUpdate,tvLastValue 
FROM Sensors

Utilizo este método para los datos que se consultan con frecuencia. En mi caso, tengo una tabla de sensores y una tabla de eventos grande, que tienen datos que ingresan a nivel de minutos Y docenas de máquinas están actualizando tableros y gráficos con esos datos. Con mi escenario de datos, el método de disparo y caché funciona bien.

Hucker
fuente