Za předpokladu alespoň Postgres 9.3.
Index
Nejprve pomůže vícesloupcový index:
CREATE INDEX observations_special_idx
ON observations(station_id, created_at DESC, id)
created_at DESC
je o něco lepší, ale index by byl stále skenován pozpátku téměř stejnou rychlostí bez DESC
.
Za předpokladu created_at
je definováno NOT NULL
, jinak zvažte DESC NULLS LAST
v indexu a dotaz:
- PostgreSQL řazení podle datetime asc, nejprve null?
Poslední sloupec id
je užitečné pouze tehdy, pokud z něj získáte skenování pouze pro index, což pravděpodobně nebude fungovat, pokud budete neustále přidávat spoustu nových řádků. V tomto případě odeberte id
z indexu.
Jednodušší dotaz (stále pomalý)
Zjednodušte svůj dotaz, vnitřní podvýběr nepomůž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;
Mělo by být o něco rychlejší, ale stále pomalé.
Rychlý dotaz
- Za předpokladu, že jich máte relativně málo stanice a relativně mnoho pozorování na stanici.
- Také za předpokladu
station_id
id definováno jakoNOT NULL
.
Být skutečně rychle, potřebujete ekvivalent volného prohledávání indexů (zatím neimplementováno v Postgresu). Související odpověď:
- Optimalizujte dotaz GROUP BY pro získání nejnovějšího záznamu na uživatele
Pokud máte samostatnou tabulku stations
(což se zdá pravděpodobné), můžete to napodobit 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;
Pokud nemáte tabulku stations
, další nejlepší věcí by bylo vytvořit a udržovat jeden. Případně přidejte odkaz na cizí klíč k vynucení relační integrity.
Pokud to není možné, můžete takový stůl destilovat za chodu. Jednoduché možnosti by byly:
SELECT DISTINCT station_id FROM observations;
SELECT station_id FROM observations GROUP BY 1;
Ale obojí by potřebovalo sekvenční skenování a bylo by pomalé. Nastavte Postgres, aby používal výše uvedený index (nebo jakýkoli index btree s station_id
jako úvodní sloupec) s rekurzivním 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
Použijte jej jako náhradu za vložení pro stations
tabulka ve výše uvedeném jednoduchém dotazu:
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;
Stále by to mělo být rychlejší než to, co jste měli o řády .
SQL Fiddle zde (9.6)
db<>Fiddle zde