Ако приемем поне 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
в индекс и заявка:
- PostgreSQL сортиране по datetime asc, първо нула?
Последната колона 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} -- your limit here
ORDER BY station_id, created_at DESC;
Би трябвало да е малко по-бързо, но все пак бавно.
Бърза заявка
- Ако приемем, че имате относително малко станциите и относително много наблюдения на станция.
- Приемаме също
station_id
идентификатор, дефиниран катоNOT NULL
.
Да бъдем наистина бързо, имате нужда от еквивалента на разхлабено сканиране на индекс (все още не е внедрен в Postgres). Свързан отговор:
- Оптимизирайте заявката GROUP BY, за да извлечете последния запис на потребител
Ако имате отделна таблица с stations
(което изглежда вероятно), можете да емулирате това с JOIN LATERAL
(Postgres 9.3+):
SELECT o.id
FROM stations s
CROSS JOIN LATERAL (
SELECT o.id
FROM observations o
WHERE o.station_id = s.station_id -- lateral reference
ORDER BY o.created_at DESC
LIMIT #{n} -- your limit here
) o
ORDER BY s.station_id, o.created_at DESC;
Ако нямате таблица с stations
, следващото най-добро нещо би било да създадете и поддържате такъв. Възможно е да добавите препратка към външен ключ, за да наложите релационната цялост.
Ако това не е опция, можете да дестилирате такава маса в движение. Простите опции биха били:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Но и двете ще се нуждаят от последователно сканиране и ще бъдат бавни. Накарайте 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 o.station_id
FROM observations o
WHERE o.station_id > s.station_id
ORDER BY o.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
Използвайте го като замяна за stations
таблица в горната проста заявка:
WITH RECURSIVE stations AS (
(
SELECT station_id
FROM observations
ORDER BY station_id
LIMIT 1
)
UNION ALL
SELECT (SELECT o.station_id
FROM observations o
WHERE o.station_id > s.station_id
ORDER BY o.station_id
LIMIT 1)
FROM stations s
WHERE s.station_id IS NOT NULL
)
SELECT o.id
FROM stations s
CROSS JOIN LATERAL (
SELECT o.id, o.created_at
FROM observations o
WHERE o.station_id = s.station_id
ORDER BY o.created_at DESC
LIMIT #{n} -- your limit here
) o
WHERE s.station_id IS NOT NULL
ORDER BY s.station_id, o.created_at DESC;
Това все пак трябва да е по-бързо от това, което сте имали спорядък на величина .
SQL Fiddle тук (9.6)
db<>Fiddle тук