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.