Förutsatt minst Postgres 9.3.
Index
Först kommer ett index med flera kolumner att hjälpa:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
passar något bättre, men indexet skulle fortfarande skannas bakåt i nästan samma hastighet utan DESC
.
Förutsatt created_at
är definierad NOT NULL
, annars överväga DESC NULLS LAST
i index och fråga:
- PostgreSQL sortera efter datetime asc, null först?
Den sista kolumnen id
är bara användbart om du får en index-skanning av den, vilket förmodligen inte kommer att fungera om du lägger till många nya rader hela tiden. Ta i så fall bort id
från indexet.
Enklare fråga (fortfarande långsam)
Förenkla din fråga, det inre undervalet hjälper inte:
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;
Borde vara lite snabbare, men ändå långsamt.
Snabb fråga
- Förutsatt att du har relativt få stationer och relativt många observationer per station.
- Antar även
station_id
id definierat somNOT NULL
.
Att vara på riktigt snabbt, du behöver motsvarande en lös indexskanning (inte implementerat i Postgres, ännu). Relaterat svar:
- Optimera GROUP BY-frågan för att hämta senaste posten per användare
Om du har en separat tabell över stations
(vilket verkar troligt), du kan emulera detta 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;
Om du inte har en tabell över stations
, det näst bästa vore att skapa och underhålla en. Lägg eventuellt till en främmande nyckelreferens för att upprätthålla relationsintegritet.
Om det inte är ett alternativ kan du destillera ett sådant bord i farten. Enkla alternativ skulle vara:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Men båda skulle behöva en sekventiell genomsökning och vara långsam. Låt Postgres använda ovanstående index (eller vilket btree-index som helst med station_id
som ledande kolumn) 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
Använd det som drop-in-ersättning för stations
tabell i ovanstående enkla fråga:
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;
Detta borde fortfarande vara snabbare än vad du hade i storleksordningar .
SQL Fiddle här (9.6)
db<>fiol här