sql >> Databasteknik >  >> RDS >> PostgreSQL

Välj första raden i varje GROUP BY-grupp?

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 matcha ORDER 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 varje DISTINCT 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 eller ORDER 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 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.



  1. Hur man hittar tabeller som innehåller en specifik kolumn i SQL Server

  2. Använder olika databaser olika namncitat?

  3. Uppgradering av en varchar-kolumn till enumtyp i postgresql

  4. Vad kan orsaka intermittenta ORA-12519 (TNS:ingen lämplig hanterare hittades) fel