web-dev-qa-db-fra.com

Comment puis-je sélectionner des lignes avec l'horodatage le plus récent pour chaque valeur de clé?

J'ai un tableau de données de capteur. Chaque ligne a un identifiant de capteur, un horodatage et d'autres champs. Je souhaite sélectionner une seule ligne avec le dernier horodatage pour chaque capteur, y compris certains des autres champs.

Je pensais que la solution serait de regrouper par ID de capteur puis par ordre de max (horodatage) comme suit:

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

Cela me donne une erreur en disant que "sensorField1 doit apparaître dans la clause group by ou doit être utilisé dans un agrégat".

Quelle est la bonne façon d'aborder ce problème?

61
franklynd

Par souci d'exhaustivité, voici une autre solution possible:

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;

C'est assez explicite, je pense, mais voici plus d'infos si vous le souhaitez, ainsi que d'autres exemples. Cela provient du manuel MySQL, mais la requête ci-dessus fonctionne avec tous les SGBDR (implémentant la norme sql'92).

70
fancyPants

Ceci peut être fait de manière relativement élégante en utilisant SELECT DISTINCT, comme suit:

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

Ce qui précède fonctionne pour PostgreSQL (quelques informations supplémentaires ici ), mais je pense aussi à d’autres moteurs. Au cas où ce ne serait pas évident, cela trierait la table par identifiant de capteur et horodatage (du plus ancien au plus ancien), puis renverrait la première ligne (c'est-à-dire le dernier horodatage) pour chaque identifiant de capteur unique.

Dans mon cas d'utilisation, j'ai environ 10 millions de lectures provenant de capteurs ~ 1K, essayer de joindre la table avec lui-même sur un filtre basé sur un horodatage nécessite beaucoup de ressources; ce qui précède prend quelques secondes.

29
Svet

Vous pouvez joindre la table à elle-même (sur l'ID du capteur) et ajouter left.timestamp < right.timestamp en tant que condition de jointure. Ensuite, vous sélectionnez les lignes, où right.id est null. Voilà, vous avez la dernière entrée par capteur.

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)

Mais notez bien que cela demandera beaucoup de ressources si vous avez un petit nombre d'identifiants et de nombreuses valeurs! Donc, je ne le recommanderais pas pour une sorte de mesure, où chaque capteur recueille une valeur chaque minute. Cependant, dans un cas d'utilisation, où vous devez suivre les "révisions" de quelque chose qui change juste "parfois", c'est facile.

17
dognose

Vous pouvez uniquement sélectionner des colonnes appartenant au groupe ou utilisées dans une fonction d'agrégation. Vous pouvez utiliser une jointure pour que cela fonctionne

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
16
juergen d
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
3
Joel Coehoorn

comme a répondu @fancyPants

SELECT sensorID,timestamp,sensorField1,sensorField2 
FROM sensorTable stmt_outer
WHERE timestamp = (SELECT MAX(timestamp) FROM sensorTable stmt_inner WHERE outer.sensorID = inner.sensorID)

cela s'appelle Correlated Subqueries et est différent des sous-requêtes imbriquées normales
i.ee: chaque sous-requête est exécutée une fois pour chaque ligne de la requête externe.
Cela signifie que la sous-requête interne:

(SELECT MAX(timestamp) FROM sensorTable inner WHERE outer.sensorID = inner.sensorID)

va être exécuté pour chaque ligne, résultant en colonne contient le max (timestamp) qui est ensuite comparé à la colonne externe pour sélectionner un seul sensor_id distinct de l'instruction externe

1
Emad Saeed

J'avais surtout le même problème et une solution différente qui rend ce type de problème trivial à interroger.

J'ai un tableau de données de capteurs (données d'une minute sur environ 30 capteurs)

SensorReadings->(timestamp,value,idSensor)

et j'ai une table de capteurs qui contient beaucoup d'éléments essentiellement statiques sur le capteur, mais les champs pertinents sont les suivants:

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

TvLastupdate et tvLastValue sont définies dans un déclencheur sur des insertions dans la table SensorReadings. J'ai toujours un accès direct à ces valeurs sans avoir à faire des requêtes coûteuses. Cela dénormalise légèrement. La requête est triviale:

SELECT idSensor,Description,tvLastUpdate,tvLastValue 
FROM Sensors

J'utilise cette méthode pour les données fréquemment interrogées. Dans mon cas, j'ai une table de capteurs et une grande table d'événements contenant des données entrant au niveau des minutes ET des dizaines de machines mettant à jour des tableaux de bord et des graphiques avec ces données. Avec mon scénario de données, la méthode trigger-and-cache fonctionne bien.

0
Hucker