DISTINCT ON
is hiervoor doorgaans het eenvoudigst en snelst in PostgreSQL .
(Zie hieronder voor prestatie-optimalisatie voor bepaalde workloads.)
SELECT DISTINCT ON (customer)
id, customer, total
FROM purchases
ORDER BY customer, total DESC, id;
Of korter (indien niet zo duidelijk) met ordinale aantallen uitvoerkolommen:
SELECT DISTINCT ON (2)
id, customer, total
FROM purchases
ORDER BY 2, 3 DESC, 1;
Als total
kan NULL zijn (kan hoe dan ook geen kwaad, maar u wilt wel overeenkomen met bestaande indexen):
...
ORDER BY customer, total DESC NULLS LAST, id;
Belangrijkste punten
DISTINCT ON
is een PostgreSQL-extensie van de standaard (waar alleen DISTINCT
in het algemeen SELECT
lijst is gedefinieerd).
Maak een lijst van een willekeurig aantal uitdrukkingen in de DISTINCT ON
clausule, definieert de gecombineerde rijwaarde duplicaten. De handleiding:
Het is duidelijk dat twee rijen als verschillend worden beschouwd als ze in ten minste één kolomwaarde verschillen. Null-waarden worden in deze vergelijking als gelijk beschouwd.
Vetgedrukte nadruk van mij.
DISTINCT ON
kan worden gecombineerd met ORDER BY
. Voorlopende uitdrukkingen in ORDER BY
moet in de reeks uitdrukkingen in DISTINCT ON
. staan , maar u kunt de volgorde daar vrijelijk tussen herschikken. Voorbeeld.
U kunt extra . toevoegen uitdrukkingen naar ORDER BY
om een bepaalde rij te kiezen uit elke groep peers. Of, zoals de handleiding het stelt:
De
DISTINCT ON
uitdrukking(en) moeten overeenkomen met de meest linkseORDER BY
uitdrukking(en). DeORDER BY
clausule bevat normaal gesproken aanvullende expressie(s) die de gewenste prioriteit bepalen van rijen binnen elkeDISTINCT ON
groep.
Ik heb id
toegevoegd als laatste item om banden te verbreken:
"Kies de rij met de kleinste id
van elke groep met het hoogste total
."
Als u de resultaten wilt ordenen op een manier die niet overeenkomt met de sorteervolgorde die de eerste per groep bepaalt, kunt u de bovenstaande zoekopdracht nesten in een buitenste zoekopdracht met een andere ORDER BY
. Voorbeeld.
Als total
kan NULL zijn, u waarschijnlijk wil de rij met de grootste niet-null-waarde. Voeg NULLS LAST
toe zoals aangetoond. Zie:
- Sorteren op kolom ASC, maar NULL-waarden eerst?
De SELECT
lijst wordt niet beperkt door uitdrukkingen in DISTINCT ON
of ORDER BY
hoe dan ook. (Niet nodig in het eenvoudige geval hierboven):
-
U hoeft niet neem een van de uitdrukkingen op in
DISTINCT ON
ofORDER BY
. -
Je kunt neem elke andere uitdrukking op in de
SELECT
lijst. Dit is essentieel voor het vervangen van veel complexere zoekopdrachten door subquery's en aggregatie-/vensterfuncties.
Ik heb getest met Postgres-versies 8.3 - 13. Maar de functie is er in ieder geval sinds versie 7.1, dus eigenlijk altijd.
Index
De perfecte index voor de bovenstaande zoekopdracht zou een index met meerdere kolommen zijn die alle drie de kolommen omvat in overeenkomende volgorde en met overeenkomende sorteervolgorde:
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
Misschien te gespecialiseerd. Maar gebruik het als leesprestaties voor de specifieke query cruciaal zijn. Als u DESC NULLS LAST
. heeft gebruik in de query hetzelfde in de index zodat de sorteervolgorde overeenkomt en de index van toepassing is.
Effectiviteit/Prestatie-optimalisatie
Weeg kosten en baten af voordat u aangepaste indexen maakt voor elke zoekopdracht. Het potentieel van bovenstaande index hangt grotendeels af van gegevensdistributie .
De index wordt gebruikt omdat deze voorgesorteerde gegevens levert. In Postgres 9.2 of later kan de zoekopdracht ook profiteren van een alleen-index scan als de index kleiner is dan de onderliggende tabel. De index moet echter in zijn geheel worden gescand.
Voor enkele rijen per klant (hoge kardinaliteit in kolom customer
), dit is zeer efficiënt. Zeker als je toch gesorteerde output nodig hebt. Het voordeel neemt af met een groeiend aantal rijen per klant.
Idealiter heeft u genoeg work_mem
om de betrokken sorteerstap in RAM te verwerken en niet naar schijf te morsen. Maar over het algemeen instellen van work_mem
ook hoog kan nadelige effecten hebben. Overweeg SET LOCAL
voor uitzonderlijk grote vragen. Vind hoeveel je nodig hebt met EXPLAIN ANALYZE
. Vermelding van "Schijf: " in de sorteerstap geeft aan dat er meer nodig is:
- Configuratieparameter work_mem in PostgreSQL op Linux
- Eenvoudige zoekopdracht optimaliseren met ORDER BY-datum en tekst
Voor velen rijen per klant (lage kardinaliteit in kolom customer
), een losse indexscan (ook bekend als "scan overslaan") zou (veel) efficiënter zijn, maar dat is niet geïmplementeerd tot Postgres 14. (Een implementatie voor alleen-index scans is in ontwikkeling voor Postgres 15. Zie hier en hier.)
Voor nu zijn er snellere zoektechnieken hiervoor te vervangen. Vooral als je een aparte tabel hebt met unieke klanten, wat een typisch geval is. Maar ook als u dat niet doet:
- SELECT DISTINCT is langzamer dan verwacht op mijn tafel in PostgreSQL
- Optimaliseer de GROUP BY-query om de laatste rij per gebruiker op te halen
- Optimaliseer groepsgewijze maximale zoekopdracht
- Bezoek laatste N gerelateerde rijen per rij
Benchmarks
Zie apart antwoord.