Uitgaande van ten minste Postgres 9.3.
Index
Ten eerste zal een index met meerdere kolommen helpen:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
past iets beter, maar de index zou nog steeds met bijna dezelfde snelheid achteruit worden gescand zonder DESC
.
Ervan uitgaande dat created_at
is gedefinieerd NOT NULL
, overweeg anders DESC NULLS LAST
in index en vraag:
- PostgreSQL sorteren op datum/tijd asc, eerst null?
De laatste kolom id
is alleen handig als je er een scan met alleen index uit haalt, wat waarschijnlijk niet zal werken als je constant veel nieuwe rijen toevoegt. Verwijder in dit geval id
uit de index.
Eenvoudigere zoekopdracht (nog steeds traag)
Vereenvoudig je zoekopdracht, de binnenste subselect helpt niet:
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;
Zou iets sneller moeten zijn, maar nog steeds traag.
Snelle zoekopdracht
- Ervan uitgaande dat je relatief weinig . hebt zenders en relatief veel waarnemingen per station.
- Ook uitgaande van
station_id
id gedefinieerd alsNOT NULL
.
Om echt te zijn snel is, hebt u het equivalent van een losse indexscan nodig (nog niet geïmplementeerd in Postgres). Gerelateerd antwoord:
- Optimaliseer de GROUP BY-query om het laatste record per gebruiker op te halen
Als je een aparte tabel hebt met stations
(wat waarschijnlijk lijkt), kun je dit emuleren met 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;
Als je geen tabel hebt met stations
, zou het beste zijn om er een te maken en te onderhouden. Voeg eventueel een refererende sleutelreferentie toe om relationele integriteit af te dwingen.
Als dat geen optie is, kun je zo'n tabel on-the-fly distilleren. Eenvoudige opties zijn:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Maar beide zouden een sequentiële scan nodig hebben en traag zijn. Laat Postgres bovenstaande index gebruiken (of een willekeurige btree-index met station_id
als leidende kolom) met een recursieve 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
Gebruik dat als drop-in vervanging voor de stations
tabel in de bovenstaande eenvoudige vraag:
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;
Dit zou nog steeds sneller moeten zijn dan wat je had met orden van grootte .
SQL Fiddle hier (9.6)
db<>fiddle hier