SQL. Запрос на вывод самых актуальных значений

Имеются две таблицы (Версия сервера: 10.6.16-MariaDB-0ubuntu0.22.04.1 - Ubuntu 22.04) со следующим содержимым:

Таблица sensors

sensor_id sensor_adress
1 03173311a0ff
2 xxxxxxxxxxxx

...и таблица measurements

measurement_id sensor_id temperature_value measurement_time
1 1 18.567 2024-03-10 19:00:30
2 2 19.123 2024-03-10 19:00:40
3 1 20.0 2024-03-10 20:00:30
4 2 22.5 2024-03-10 20:00:40

Задача: Вывести самую актуальную температуру (temperature_value) по всем датчикам и адрес сенсора (sensor_adress), т.е. что-то в этом роде:

sensor_adress temperature_value
03173311a0ff 20.0
xxxxxxxxxxxx 22.5

Пробовал составить SQL-запрос, получилось вот что:

SELECT `sensors`.`sensor_adress`, `measurements`.`temperature_value`
FROM `measurements` INNER JOIN `sensors`
ON `sensors`.`sensor_id` = `measurements`.`sensor_id`
WHERE `measurements`.`measurement_time` = (SELECT MAX(`measurement_time`) FROM `measurements`)
ORDER BY `sensors`.`sensor_id` ASC;

Как мне улучшить подзапрос

SELECT MAX(`measurement_time`) FROM `measurements`

чтобы была выборка не по максимальному TIMESTAMP, а именно по самому актуальному для каждого датчика?


Ответы (1 шт):

Автор решения: teran

Для решения подобных задач обычно имеются следующие варианты:

  1. использование производной таблицы/общего табличного выражения с группировкой для поиска максимального значения для сенсора, и джойн этого добра на саму таблицу данных. Примерно так:

    SELECT s.address, d.temperature_value
    FROM sensors AS s 
    INNER JOIN (
       SELECT sensor_id, max(id) as id
       FROM measurements
       GROUP BY sensor_id
    ) AS m ON (m.sensor_id = s.id)
    INNER JOIN measurements AS d ON (d.id = m.id)
    
    # ---- либо аналогичный CTE
    WITH m AS (
        SELECT sensor_id, MAX(id) AS id
        ....
    )
    SELECT 
    FROM sensors AS s 
    INNER JOIN m ....
    

    тип джойнов тут будет определять, что первично, список имеющихся данных или сенсоров.

  2. Оконные функции и нумерация строк

    WITH data AS (
       SELECT sensor_id
           , temperature_value
           , ROW_NUMBER() OVER (PARTITION BY sensor_id ORDER BY id DESC) AS rn
       FROM measurements AS m
    ) 
    SELECT s.address, data.temperature_value
    FROM data 
    INNER JOIN sensors AS s ON (s.id = data.sensor_id)
    WHERE rn = 1 
    
  3. Коррелирующий подзапрос,который возвращает единственное значение температуры для сенсора:

    SELECT s.address
        , (SELECT temperature_value
           FROM measurements AS m
           WHERE m.sensor_id = s.id
           ORDER BY id DESC 
           LIMIT 1) AS t
    FROM sensors AS s 
    
  4. Если же кроме одного значения надо вытащить и другие данные из строк, то подзапрос уходит в джойн

     SELECT s.address, m.temperature_value
     FROM sensors AS s 
     LEFT JOIN measurements AS m ON (
           m.id = (SELECT MAX(id) 
                   FROM measurements AS m 
                   WHERE m.sensor_id = s.id)
     )
    

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

→ Ссылка