sql >> Database >  >> RDS >> PostgreSQL

Selecteer de eerste rij in elke GROUP BY-groep?

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 linkse ORDER BY uitdrukking(en). De ORDER BY clausule bevat normaal gesproken aanvullende expressie(s) die de gewenste prioriteit bepalen van rijen binnen elke DISTINCT 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 of ORDER 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.



  1. Hoe beïnvloeden IMMUTABLE, STABLE en VOLATILE trefwoorden het gedrag van de functie?

  2. Tips om SQL Server-database van de ene server naar de andere te verplaatsen - SQL-zelfstudie door Rajan Singh

  3. MySQL-server is verdwenen - over precies 60 seconden

  4. 2 manieren om alle functies in MariaDB weer te geven