DISTINCT ON
är vanligtvis enklast och snabbast för detta i PostgreSQL .
(För prestandaoptimering för vissa arbetsbelastningar, se nedan.)
SELECT DISTINCT ON (customer)
id, customer, total
FROM purchases
ORDER BY customer, total DESC, id;
Eller kortare (om inte lika tydligt) med ordningsnummer för utdatakolumner:
SELECT DISTINCT ON (2)
id, customer, total
FROM purchases
ORDER BY 2, 3 DESC, 1;
Om total
kan vara NULL (skadar inte på något sätt, men du vill matcha befintliga index):
...
ORDER BY customer, total DESC NULLS LAST, id;
Huvudpunkter
DISTINCT ON
är en PostgreSQL-förlängning av standarden (där endast DISTINCT
på det hela taget SELECT
listan är definierad).
Lista valfritt antal uttryck i DISTINCT ON
sats, definierar det kombinerade radvärdet dubbletter. Manualen:
Uppenbarligen anses två rader vara distinkta om de skiljer sig åt i minst ett kolumnvärde. Nullvärden anses vara lika i denna jämförelse.
Djärv betoning min.
DISTINCT ON
kan kombineras med ORDER BY
. Ledande uttryck i ORDER BY
måste finnas i uppsättningen uttryck i DISTINCT ON
, men du kan ändra ordning bland dessa fritt. Exempel.
Du kan lägga till ytterligare uttryck till ORDER BY
att välja en viss rad från varje grupp av kamrater. Eller, som bruksanvisningen uttrycker det:
DISTINCT ON
uttryck måste matchaORDER BY
längst till vänster uttryck.ORDER BY
sats kommer normalt att innehålla ytterligare uttryck som bestämmer den önskade prioriteringen av rader inom varjeDISTINCT ON
grupp.
Jag lade till id
som sista objekt för att bryta band:
"Välj raden med det minsta id
från varje grupp som delar den högsta total
."
För att sortera resultaten på ett sätt som inte stämmer överens med sorteringsordningen som bestämmer den första per grupp, kan du kapsla ovanstående fråga i en yttre fråga med en annan ORDER BY
. Exempel.
Om total
kan vara NULL, du antagligen vill ha raden med det största icke-nullvärdet. Lägg till NULLS LAST
som visat. Se:
- Sortera efter kolumn ASC, men NULL-värden först?
Koden SELECT
lista är inte begränsad av uttryck i DISTINCT ON
eller ORDER BY
på något sätt. (Behövs inte i det enkla fallet ovan):
-
Du behöver inte inkludera något av uttrycken i
DISTINCT ON
ellerORDER BY
. -
Du kan inkludera något annat uttryck i
SELECT
lista. Detta är avgörande för att ersätta mycket mer komplexa frågor med underfrågor och aggregat-/fönsterfunktioner.
Jag testade med Postgres version 8.3 – 13. Men funktionen har funnits där åtminstone sedan version 7.1, så i princip alltid.
Index
Det perfekta index för ovanstående fråga skulle vara ett index med flera kolumner som spänner över alla tre kolumner i matchande sekvens och med matchande sorteringsordning:
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
Kan vara för specialiserad. Men använd den om läsprestanda för den specifika frågan är avgörande. Om du har DESC NULLS LAST
i frågan, använd samma i indexet så att sorteringsordningen matchar och indexet är tillämpligt.
Effektivitet/prestandaoptimering
Väg kostnad och nytta innan du skapar skräddarsydda index för varje fråga. Potentialen för ovanstående index beror till stor del på datadistribution .
Indexet används eftersom det levererar försorterad data. I Postgres 9.2 eller senare kan frågan också dra nytta av en endast indexskanning om indexet är mindre än den underliggande tabellen. Indexet måste dock skannas i sin helhet.
För få rader per kund (hög kardinalitet i kolumnen customer
), detta är mycket effektivt. Ännu mer så om du ändå behöver sorterad utdata. Fördelen minskar med ett växande antal rader per kund.
Helst sett har du tillräckligt med work_mem
att bearbeta det involverade sorteringssteget i RAM och inte spilla till disken. Men generellt ställer du in work_mem
också hög kan ha negativa effekter. Överväg SET LOCAL
för exceptionellt stora frågor. Hitta hur mycket du behöver med EXPLAIN ANALYZE
. Omnämnande av "Disk: " i sorteringssteget indikerar behovet av mer:
- Konfigurationsparameter work_mem i PostgreSQL på Linux
- Optimera enkel fråga med BESTÄLLNING EFTER datum och text
För många rader per kund (låg kardinalitet i kolumnen customer
), en lös indexskanning (a.k.a. "skip scan") skulle vara (mycket) mer effektivt, men det är inte implementerat fram till Postgres 14. (En implementering för index-endast skanningar är under utveckling för Postgres 15. Se här och här.)
För nu finns det snabbare frågetekniker att ersätta detta. I synnerhet om du har ett separat bord med unika kunder, vilket är det typiska användningsfallet. Men också om du inte gör det:
- SELECT DISTINCT är långsammare än förväntat på mitt bord i PostgreSQL
- Optimera GROUP BY-frågan för att hämta den senaste raden per användare
- Optimera gruppvis maximal fråga
- Fråga de sista N relaterade raderna per rad
Benchmarks
Se separat svar.