Ответ 1
Предполагая текущую версию Postgres 9.3.
Индекс
Во-первых, индекс с несколькими столбцами поможет:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
немного лучше подходит, но индекс все равно будет сканироваться назад с почти той же скоростью без DESC
.
Предполагая, что created_at
определен NOT NULL
, еще рассмотрите DESC NULLS LAST
в индексе и запросе:
Последний столбец id
полезен только в том случае, если вы получаете сканирование с индексом из этого, что, вероятно, не будет работать если вы добавляете много новых строк постоянно. В этом случае удалите id
из индекса.
Упрощенный запрос (все еще медленный)
Упростите свой запрос, внутренний подзапрос не поможет:
SELECT id
FROM (
SELECT station_id, id, created_at
, row_number() OVER (PARTITION BY station_id
ORDER BY created_at DESC) AS rn
FROM observations
) s
WHERE rn <= #{n}
ORDER BY station_id, created_at DESC;
Должно быть немного быстрее, но все же медленно.
Быстрый запрос
- Предполагая, что у вас относительно несколько станций и относительно много наблюдений на станцию.
- Также предполагается, что
station_id
id определяется какNOT NULL
.
Чтобы быть действительно быстрым, вам нужен эквивалент свободного индекса сканирования (не реализованного в Postgres). Связанный ответ:
Если у вас есть отдельная таблица stations
(что кажется вероятным), вы можете эмулировать ее с помощью JOIN LATERAL
(Postgres 9.3 +):
SELECT o.id
FROM stations s
JOIN LATERAL (
SELECT id, created_at
FROM observations
WHERE station_id = s.id -- lateral reference
ORDER BY created_at DESC
LIMIT #{n}
) o ON TRUE
ORDER BY s.id, o.created_at DESC;
Если у вас нет таблицы stations
, следующая лучшая вещь - создать и поддерживать ее. Возможно, добавьте ссылку на внешний ключ, чтобы обеспечить реляционную целостность.
Если это не вариант, вы можете перетащить такую таблицу на лету. Простые опции:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Но для этого потребуется последовательное сканирование и быть слишком медленным. Trick Postgres в использовании вышеуказанного индекса (или любого индекса btree с station_id
в качестве ведущего столбца) с рекурсивным CTE:
WITH RECURSIVE stations AS (
( -- extra pair of parentheses ...
SELECT station_id
FROM observations
ORDER BY station_id
LIMIT 1
) -- ... is required!
UNION ALL
SELECT (SELECT station_id
FROM observations
WHERE station_id > s.station_id
ORDER BY station_id
LIMIT 1)
FROM stations s
WHERE s.station_id IS NOT NULL -- serves as break condition
)
SELECT station_id
FROM stations
WHERE station_id IS NOT NULL; -- remove dangling row with NULL
Используйте это как drop-in replacement для таблицы stations
в приведенном выше простом запросе:
WITH RECURSIVE stations AS (
(
SELECT station_id
FROM observations
ORDER BY station_id
LIMIT 1
)
UNION ALL
SELECT (SELECT station_id
FROM observations
WHERE station_id > s.station_id
ORDER BY station_id
LIMIT 1)
FROM stations s
WHERE s.station_id IS NOT NULL
)
SELECT o.id
FROM stations s
JOIN LATERAL (
SELECT id, created_at
FROM observations
WHERE station_id = s.station_id
ORDER BY created_at DESC
LIMIT #{n}
) o ON TRUE
WHERE s.station_id IS NOT NULL
ORDER BY s.station_id, o.created_at DESC;
Это должно быть быстрее, чем у вас порядков.