Como posso selecionar linhas com carimbo de data / hora mais recente para cada valor-chave?


86

Eu tenho uma tabela de dados do sensor. Cada linha possui um id de sensor, um carimbo de data / hora e outros campos. Quero selecionar uma única linha com o carimbo de data / hora mais recente para cada sensor, incluindo alguns dos outros campos.

Pensei que a solução seria agrupar por id de sensor e depois ordenar por max (timestamp) assim:

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

Isso me dá um erro dizendo que "sensorField1 deve aparecer na cláusula group by ou ser usado em um agregado."

Qual é a forma correta de abordar este problema?


1
Qual motor DB você está usando?
— juergen d

1
Embora as respostas abaixo usando JOINs no valor Max (timestamp) devam funcionar, eu sugeriria juntar em um SensorReadingId se você tiver um na sensorTable.
— Thomas Langston

Respostas:


94

Para fins de integridade, aqui está outra solução possível:

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;

Muito autoexplicativo, eu acho, mas aqui estão mais informações, se desejar, assim como outros exemplos. É do manual do MySQL, mas a consulta acima funciona com todos os RDBMS (implementando o padrão sql'92).


56

Isso pode ser feito de uma maneira relativamente elegante SELECT DISTINCT, usando o seguinte:

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

O acima funciona para PostgreSQL (mais algumas informações aqui ), mas acho que também outros motores. Caso não seja óbvio, o que isso faz é classificar a tabela por ID do sensor e registro de data e hora (do mais recente para o mais antigo) e, em seguida, retorna a primeira linha (ou seja, o registro de data e hora mais recente) para cada ID de sensor exclusivo.

No meu caso de uso, tenho aproximadamente 10 milhões de leituras de sensores de aproximadamente 1 mil, portanto, tentar juntar a tabela a ela mesma em um filtro baseado em carimbo de data / hora consome muitos recursos; o procedimento acima leva alguns segundos.


Esta solução é muito rápida.
— Ena

Rápido e fácil de entender. Obrigado por explicar o caso de uso também, pois o meu é bastante semelhante.
— Stef Verdonk

Infelizmente, isso não funciona para MySQL ( link )
— silentsurfer

21

Você pode unir a tabela a ela mesma (na id do sensor) e adicionar left.timestamp < right.timestampcomo condição de união. Então você escolhe as linhas, onde right.idestá null. Voila, você tem a ú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)

Mas observe que isso consumirá muitos recursos se você tiver uma pequena quantidade de ids e muitos valores! Então, eu não recomendaria isso para algum tipo de material de medição, onde cada sensor coleta um valor a cada minuto. No entanto, em um Caso de Uso, em que você precisa controlar as "Revisões" de algo que muda apenas "às vezes", é fácil.


Isso é mais rápido do que outras respostas, pelo menos no meu caso.
— chuva_

@rain_ Isso realmente depende do caso de uso. Portanto, não há uma "resposta universal" para essa pergunta.
— dognose

19

Você só pode selecionar colunas que estão no grupo ou usadas em uma função agregada. Você pode usar uma junção para fazer isso funcionar

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

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

Acho que "LEFT JOIN" também se aplica, não apenas "INNER JOIN"; e uma parte "e s1.timestamp = s2.mts" não é necessário IMHO. E ainda, eu aconselho criar índice em dois campos: sensorID + timestamp - a velocidade da consulta aumenta muito!
— 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

Há uma resposta comum que ainda não vi aqui, que é a função de janela. É uma alternativa à subconsulta correlacionada, se seu banco de dados oferecer suporte.

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;

Eu costumo usar isso mais do que subconsultas correlatas. Sinta-se à vontade para me criticar nos comentários sobre eficácia, não tenho muita certeza de como isso se encaixa nesse aspecto.


0

Eu tinha praticamente o mesmo problema e acabei encontrando uma solução diferente que torna esse tipo de problema trivial de consultar.

Eu tenho uma tabela de dados do sensor (dados de 1 minuto de cerca de 30 sensores)

SensorReadings->(timestamp,value,idSensor)

e eu tenho uma tabela de sensores que contém muitas coisas estáticas sobre o sensor, mas os campos relevantes são estes:

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

O tvLastupdate e o tvLastValue são definidos em um acionador em inserções na tabela SensorReadings. Sempre tenho acesso direto a esses valores, sem precisar fazer consultas caras. Isso desnormaliza ligeiramente. A consulta é trivial:

SELECT idSensor,Description,tvLastUpdate,tvLastValue 
FROM Sensors

Eu uso esse método para dados que são consultados com frequência. No meu caso, tenho uma tabela de sensores e uma grande tabela de eventos com dados chegando em nível de minuto E dezenas de máquinas estão atualizando painéis e gráficos com esses dados. Com meu cenário de dados, o método de gatilho e cache funciona bem.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.