Как выбрать строки с самой последней меткой времени для каждого значения ключа?

86

У меня есть таблица данных датчиков. В каждой строке есть идентификатор датчика, отметка времени и другие поля. Я хочу выбрать одну строку с последней меткой времени для каждого датчика, включая некоторые другие поля.

Я думал, что решением будет сгруппировать по идентификатору датчика, а затем упорядочить по максимальному (временная метка) следующим образом:

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

Это дает мне сообщение об ошибке, в котором говорится, что «sensorField1 должен находиться в предложении group by или использоваться в совокупности».

Как правильно подойти к этой проблеме?

откровенно
источник
1
Какой движок БД вы используете?
juergen d
1
Хотя приведенные ниже ответы с использованием JOINs для значения Max (timestamp) должны работать, я бы предложил присоединиться к SensorReadingId, если он у вас есть в sensorTable.
Thomas Langston

Ответы:

94

Для полноты картины вот еще одно возможное решение:

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;

Я думаю, это довольно самоочевидно, но вот дополнительная информация, если хотите, а также другие примеры. Это из руководства MySQL, но указанный выше запрос работает со всеми СУБД (реализующими стандарт sql'92).

выпендрежник
источник
56

Это можно сделать относительно элегантным способом, используя SELECT DISTINCTследующее:

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

Вышеупомянутое работает для PostgreSQL (дополнительная информация здесь ), но я думаю, также и для других движков. В случае, если это не очевидно, это сортирует таблицу по идентификатору датчика и метке времени (от самого нового к самому старому), а затем возвращает первую строку (т.е. самую последнюю метку времени) для каждого уникального идентификатора датчика.

В моем случае использования у меня есть ~ 10 млн показаний с ~ 1 тыс. Датчиков, поэтому попытка объединить таблицу сама с собой с помощью фильтра на основе временных меток очень ресурсоемка; вышесказанное занимает пару секунд.

Свет
источник
Это решение действительно быстрое.
Ena
Быстро и легко понять. Спасибо за объяснение варианта использования, так как мой вариант очень похож.
Стеф Вердонк
К сожалению, это не работает для MySQL ( ссылка )
Silentsurfer
21

Вы можете присоединиться к таблице с самим собой (по идентификатору датчика) и добавить в left.timestamp < right.timestampкачестве условия соединения. Затем вы выбираете строки, где right.idнаходится null. Вуаля, у вас есть последняя запись по датчику.

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)

Но учтите, что это будет очень ресурсоемко, если у вас мало идентификаторов и много значений! Поэтому я бы не рекомендовал это для каких-то измерений, когда каждый датчик собирает значение каждую минуту. Однако в случае использования, когда вам нужно отслеживать «версии» чего-то, что меняется только «иногда», это просто.

догадываться
источник
Это быстрее, чем другие ответы, по крайней мере, в моем случае.
rain_
@rain_ Это действительно зависит от варианта использования. Следовательно, на этот вопрос нет «универсального ответа».
узнал
19

Вы можете выбирать только столбцы, которые входят в группу или используются в агрегатной функции. Вы можете использовать соединение, чтобы заставить это работать

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
Юрген Д.
источник
... или select * from sensorTable where (sensorID, timestamp) in (select sensorID, max(timestamp) from sensorTable group by sensorID).
Arjan
Я думаю, что «LEFT JOIN» тоже применяется, а не только «INNER JOIN»; И ИМХО часть "а s1.timestamp = s2.mts" не нужна. И все же советую создать индекс по двум полям: sensorID + timestamp - скорость запроса увеличивается здорово!
Игорь
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

Есть один общий ответ, который я здесь еще не видел, - это оконная функция. Это альтернатива коррелированному подзапросу, если ваша БД поддерживает его.

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;

На самом деле я использую это больше, чем коррелированные подзапросы. Не стесняйтесь обвинять меня в комментариях по поводу эффективности, я не слишком уверен, как это складывается в этом отношении.

Джейми Маршалл
источник
0

У меня была в основном та же проблема, и в итоге я нашел другое решение, которое делает этот тип проблемы тривиальным для запроса.

У меня есть таблица данных датчиков (данные за 1 минуту примерно с 30 датчиков)

SensorReadings->(timestamp,value,idSensor)

и у меня есть таблица датчиков, в которой много статического материала о датчике, но соответствующие поля следующие:

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

TvLastupdate и tvLastValue устанавливаются в триггере при вставке в таблицу SensorReadings. У меня всегда есть прямой доступ к этим значениям, без необходимости выполнять какие-либо дорогостоящие запросы. Это немного денормализует. Запрос тривиальный:

SELECT idSensor,Description,tvLastUpdate,tvLastValue 
FROM Sensors

Я использую этот метод для часто запрашиваемых данных. В моем случае у меня есть таблица датчиков и большая таблица событий, в которой данные поступают на минутном уровне, И десятки машин обновляют информационные панели и графики с этими данными. В моем сценарии данных хорошо работает метод триггера и кеширования.

Hucker
источник