Як я можу вибрати рядки з найновішою позначкою часу для кожного значення ключа?


86

У мене є таблиця даних датчиків. Кожен рядок має ідентифікатор датчика, позначку часу та інші поля. Я хочу вибрати один рядок із найновішою позначкою часу для кожного датчика, включаючи деякі інші поля.

Я думав, що рішенням буде згрупування за ідентифікатором датчика, а потім упорядкування за макс.

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

Це призводить до помилки із твердженням, що "sensorField1 повинен відображатися в групі за реченням або використовуватись у сукупності".

Як правильно підійти до цієї проблеми?


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 (трохи більше інформації тут ), але я думаю, що і інші механізми. Якщо це не очевидно, це робить сортування таблиці за ідентифікатором датчика та позначкою часу (від найновішої до найстарішої), а потім повертає перший рядок (тобто останню позначку часу) для кожного унікального ідентифікатора датчика.

У моєму випадку використання я маю ~ 10M показань від ~ 1K датчиків, тому спроба приєднати таблицю до себе на фільтрі на основі мітки часу дуже ресурсомістка; вищезазначене займає пару секунд.


Це рішення дуже швидке.
Ена

Швидкий і легкий для розуміння. Дякую, що також пояснили варіант використання, оскільки мій досить схожий.
Стеф

На жаль, це не працює для 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_ Це насправді залежить від випадку використання. Тому "універсальної відповіді" на це питання немає.
доноза

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"; а частина "and s1.timestamp = s2.mts" не є необхідною IMHO. І все ж я раджу створити індекс у двох полях: 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

Я використовую цей метод для даних, які часто запитуються. У моєму випадку у мене є таблиця датчиків та велика таблиця подій, що містять дані, що надходять на рівні хвилин, І десятки машин оновлюють ці інформаційні панелі та графіки. У моєму сценарії даних метод тригера та кешування працює добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.