Zakładając co najmniej Postgres 9.3.
Indeks
Po pierwsze, wielokolumnowy indeks pomoże:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
jest nieco lepiej dopasowany, ale indeks nadal byłby skanowany wstecz z prawie taką samą prędkością bez DESC
.
Zakładając, że created_at
jest zdefiniowany NOT NULL
, w przeciwnym razie rozważ DESC NULLS LAST
w indeksie i zapytanie:
- PostgreSQL sortuje według daty i godziny asc, najpierw null?
Ostatnia kolumna id
przydaje się tylko wtedy, gdy uzyskujesz z niego tylko skan indeksu, który prawdopodobnie nie zadziała, jeśli stale dodasz wiele nowych wierszy. W takim przypadku usuń id
z indeksu.
Prostsze zapytanie (nadal wolne)
Uprość zapytanie, wewnętrzny podselekcja nie pomoże:
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;
Powinien być nieco szybszy, ale nadal wolny.
Szybkie zapytanie
- Zakładając, że masz stosunkowo niewiele stacje i stosunkowo wiele obserwacje na stację.
- Również zakładając
station_id
id zdefiniowany jakoNOT NULL
.
Być naprawdę szybko, potrzebujesz odpowiednika luźnego skanowania indeksu (jeszcze nie zaimplementowane w Postgresie). Powiązana odpowiedź:
- Zoptymalizuj zapytanie GROUP BY, aby pobrać najnowszy rekord na użytkownika
Jeśli masz oddzielną tabelę stations
(co wydaje się prawdopodobne), możesz to emulować za pomocą 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;
Jeśli nie masz tabeli stations
, następną najlepszą rzeczą byłoby utworzenie i utrzymanie takiego. Ewentualnie dodaj odwołanie do klucza obcego, aby wymusić integralność relacyjną.
Jeśli to nie jest opcja, możesz destylować taki stół w locie. Proste opcje to:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Ale albo potrzebowałby sekwencyjnego skanowania i byłby powolny. Niech Postgres używa powyższego indeksu (lub dowolnego indeksu btree z station_id
jako wiodąca kolumna) z rekurencyjnym 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
Użyj tego jako zamiennika drop-in dla stations
tabela w powyższym prostym zapytaniu:
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;
To nadal powinno być szybsze niż to, co miałeś o rzędy wielkości .
SQL Fiddle tutaj (9.6)
db<>Tutaj grać