Forudsat mindst Postgres 9.3.
Indeks
For det første vil et indeks med flere kolonner hjælpe:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
passer lidt bedre, men indekset ville stadig blive scannet baglæns med næsten samme hastighed uden DESC
.
Forudsat created_at
er defineret NOT NULL
, ellers overveje DESC NULLS LAST
i indeks og forespørgsel:
- PostgreSQL sorteres efter datetime asc, null først?
Den sidste kolonne id
er kun nyttig, hvis du får en kun indeksscanning ud af det, hvilket sandsynligvis ikke vil fungere, hvis du tilføjer masser af nye rækker konstant. I dette tilfælde skal du fjerne id
fra indekset.
Enklere forespørgsel (stadig langsom)
Forenkle din forespørgsel, det indre undervalg hjælper ikke:
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;
Bør være en smule hurtigere, men stadig langsomt.
Hurtig forespørgsel
- Forudsat at du har relativt få stationer og relativt mange observationer pr. station.
- Under forudsætning af
station_id
id defineret somNOT NULL
.
At være virkelig hurtigt, du har brug for, hvad der svarer til en løs indeksscanning (endnu ikke implementeret i Postgres). Relateret svar:
- Optimer GROUP BY-forespørgsel for at hente seneste post pr. bruger
Hvis du har en separat tabel over stations
(hvilket virker sandsynligt), du kan efterligne dette med 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;
Hvis du ikke har en tabel over stations
, ville det næstbedste være at oprette og vedligeholde en. Tilføj eventuelt en fremmednøglereference for at håndhæve relationel integritet.
Hvis det ikke er en mulighed, kan du destillere sådan et bord i farten. Enkle muligheder ville være:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Men begge ville have brug for en sekventiel scanning og være langsom. Få Postgres til at bruge ovenstående indeks (eller ethvert btree-indeks med station_id
som ledende kolonne) med en rekursiv 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
Brug det som drop-in-erstatning for stations
tabel i ovenstående simple forespørgsel:
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;
Dette burde stadig være hurtigere end hvad du havde i størrelsesordener .
SQL Fiddle her (9.6)
db<>fiddle her