Jak sugeruje tytuł, chciałbym wybrać pierwszy wiersz każdego zestawu wierszy zgrupowanych za pomocą GROUP BY
.
W szczególności, jeśli mam purchases
stół, który wygląda następująco:
SELECT * FROM purchases;
Mój wynik:
id | klient | całkowity --- + ---------- + ------ 1 | Joe | 5 2 | Sally | 3) 3 | Joe | 2) 4 | Sally | 1
Chciałbym zapytać o id
największy zakup ( total
) dokonany przez każdego customer
. Coś takiego:
SELECT FIRST(id), customer, FIRST(total)
FROM purchases
GROUP BY customer
ORDER BY total DESC;
Oczekiwany wynik:
FIRST (id) | klient | PIERWSZY (ogółem) ---------- + ---------- + ------------- 1 | Joe | 5 2 | Sally | 3)
sql
sqlite
postgresql
group-by
greatest-n-per-group
David Wolever
źródło
źródło
MAX(total)
?Odpowiedzi:
W Oracle 9.2+ (nie 8i + jak pierwotnie podano), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:
Obsługiwane przez dowolną bazę danych:
Ale musisz dodać logikę, aby zerwać więzi:
źródło
ROW_NUMBER() OVER(PARTITION BY [...])
wraz z kilkoma innymi optymalizacjami pomógł mi skrócić zapytanie z 30 sekund do kilku milisekund. Dzięki! (PostgreSQL 9.2)total
dla jednego klienta, pierwsze zapytanie zwraca dowolnego zwycięzcę (w zależności od szczegółów implementacji;id
może ulec zmianie przy każdym wykonaniu!). Zazwyczaj (nie zawsze) chciałbyś mieć jeden wiersz na klienta, zdefiniowany przez dodatkowe kryteria, takie jak „ten z najmniejszymid
”. Aby to naprawić, dołączid
doORDER BY
listyrow_number()
. Otrzymujesz taki sam wynik jak w drugim zapytaniu, co jest bardzo nieefektywne w tym przypadku. Będziesz także potrzebować innego podzapytania dla każdej dodatkowej kolumny.W PostgreSQL jest to zwykle prostsze i szybsze (więcej optymalizacji wydajności poniżej):
Lub krótszy (jeśli nie tak wyraźny) z liczbami porządkowymi kolumn wyjściowych:
Jeśli
total
może mieć wartość NULL (nie zaszkodzi w żaden sposób, ale będziesz chciał dopasować istniejące indeksy ):Najważniejsze punkty
DISTINCT ON
jest rozszerzeniem standardu PostgreSQL (gdzie zdefiniowana jest tylkoDISTINCT
całaSELECT
lista).Wymień dowolną liczbę wyrażeń w
DISTINCT ON
klauzuli, połączona wartość wiersza definiuje duplikaty. Instrukcja:Odważny nacisk moje.
DISTINCT ON
można połączyć zORDER BY
. Wyrażenia wiodące wORDER BY
muszą znajdować się w zestawie wyrażeń wDISTINCT ON
, ale możesz dowolnie zmieniać porządek między nimi. Przykład. Możesz dodać dodatkowe wyrażenia, abyORDER BY
wybrać konkretny wiersz z każdej grupy rówieśników. Lub, jak to mówi instrukcja :Dodałem
id
jako ostatni element do zerwania więzi:„Wybierz wiersz z najmniejszymi
id
z każdej grupy dzielącymi najwyższetotal
”.Aby uporządkować wyniki w sposób, który nie zgadza się z kolejnością sortowania określającą pierwszą na grupę, można zagnieździć powyższe zapytanie w zapytaniu zewnętrznym z innym zapytaniem
ORDER BY
. Przykład.Jeśli
total
może mieć wartość NULL, najprawdopodobniej chcesz wiersz o największej wartości innej niż null. DodajNULLS LAST
jak pokazano. Widzieć:SELECT
Lista nie jest ograniczony wyrażeńDISTINCT ON
lubORDER BY
w jakikolwiek sposób. (Niepotrzebne w prostym przypadku powyżej):Nie musisz dołączać żadnych wyrażeń do
DISTINCT ON
lubORDER BY
.Państwo może zawierać dowolny inny wyraz w
SELECT
liście. Jest to pomocne w zastępowaniu znacznie bardziej złożonych zapytań podkwerendami i funkcjami agregacji / okna.Testowałem z wersjami Postgres 8.3 - 12. Ale ta funkcja istnieje przynajmniej od wersji 7.1, więc w zasadzie zawsze.
Indeks
Doskonały wskaźnik dla powyższego zapytania byłoby Indeks Multi-column obejmujące wszystkie trzy kolumny i dopasowanie sekwencji z pasującymi kolejności sortowania:
Może być zbyt wyspecjalizowany. Ale użyj go, jeśli kluczowa jest wydajność odczytu dla konkretnego zapytania. Jeśli masz
DESC NULLS LAST
w zapytaniu, użyj tego samego w indeksie, aby kolejność sortowania była zgodna i indeks miał zastosowanie.Optymalizacja efektywności / wydajności
Zważ koszty i korzyści przed utworzeniem dostosowanych indeksów dla każdego zapytania. Potencjał powyższego wskaźnika w dużej mierze zależy od dystrybucji danych .
Indeks jest używany, ponieważ dostarcza wstępnie posortowane dane. W Postgresie 9.2 lub nowszym zapytanie może korzystać ze skanowania indeksu tylko wtedy, gdy indeks jest mniejszy niż tabela bazowa. Indeks należy jednak skanować w całości.
W przypadku kilku wierszy na klienta (wysoka liczność w kolumnie
customer
) jest to bardzo wydajne. Tym bardziej, jeśli i tak potrzebujesz posortowanego wyjścia. Korzyść maleje wraz ze wzrostem liczby wierszy na klienta.Idealnie, masz wystarczająco dużo,
work_mem
aby przetworzyć zaangażowany krok sortowania w pamięci RAM i nie rozlać się na dysk. Ale ogólnie ustawieniework_mem
zbyt wysoko może mieć niekorzystne skutki. RozważSET LOCAL
wyjątkowo duże zapytania. Znajdź, ile potrzebujeszEXPLAIN ANALYZE
. Wspomnienie „ Dysk: ” w kroku sortowania wskazuje na potrzebę dodatkowych:W przypadku wielu wierszy na klienta (niska liczność w kolumnie
customer
) skanowanie luźnego indeksu (inaczej „skanowanie pomijane”) byłoby (znacznie) bardziej wydajne, ale nie jest zaimplementowane do wersji Postgres 12. (Implementacja skanowania tylko za pomocą indeksu jest dostępna rozwój Postgres 13. Zobacz tutaj i tutaj .)Na razie istnieją szybsze techniki zapytań, które mogą to zastąpić. W szczególności, jeśli masz oddzielny stolik z unikalnymi klientami, co jest typowym przypadkiem użycia. Ale także jeśli nie:
Reper
Miałem tutaj prosty test porównawczy, który jest już nieaktualny. W tej osobnej odpowiedzi zastąpiłem ją szczegółowym testem porównawczym .
źródło
DISTINCT ON
staje się bardzo wolny. Implementacja zawsze sortuje całą tabelę i skanuje ją w poszukiwaniu duplikatów, ignorując wszystkie indeksy (nawet jeśli utworzono wymagany indeks wielokolumnowy). Zobacz objaśnieniextended.com/2009/05/ 03/ postgresql- optimizing- distinct, aby znaleźć możliwe rozwiązanie.SELECT
liście.DISTINCT ON
nadaje się tylko do uzyskania jednego wiersza na grupę rówieśników.Reper
Testowanie najbardziej interesujących kandydatów z PostgreSQL 9.4 i 9.5 z połowy realistycznym stole 200k wierszy w
purchases
i 10k odrębnegocustomer_id
( AVG. 20 wierszy na klienta ).W przypadku Postgres 9.5 przeprowadziłem drugi test z efektywnie 86446 różnymi klientami. Zobacz poniżej ( średnio 2,3 wiersza na klienta ).
Ustawiać
Stół główny
Używam
serial
(ograniczenie PK dodane poniżej) i liczby całkowitej,customer_id
ponieważ jest to bardziej typowa konfiguracja. Dodano również,some_column
aby uzupełnić zwykle więcej kolumn.Dummy data, PK, index - typowa tabela zawiera również kilka martwych krotek:
customer
tabela - dla zapytania nadrzędnegoW drugim teście dla 9.5 użyłem tej samej konfiguracji, ale z
random() * 100000
generowaniem,customer_id
aby uzyskać tylko kilka wierszy nacustomer_id
.Rozmiary obiektów dla tabeli
purchases
Wygenerowano za pomocą tego zapytania .
Zapytania
1.
row_number()
w CTE ( patrz inna odpowiedź )2.
row_number()
w podzapytaniu (moja optymalizacja)3.
DISTINCT ON
( zobacz inną odpowiedź )4. rCTE z
LATERAL
podzapytaniem ( patrz tutaj )5.
customer
stół zLATERAL
( patrz tutaj )6.
array_agg()
zORDER BY
( patrz inna odpowiedź )Wyniki
Czas wykonania powyższych zapytań z
EXPLAIN ANALYZE
(i wyłączonymi wszystkimi opcjami ), najlepszy z 5 uruchomień .Wszystkie zapytań używany jest Index Skanuj tylko na
purchases2_3c_idx
(wśród innych etapów). Niektóre z nich tylko dla mniejszego rozmiaru indeksu, inne bardziej efektywnie.A. Postgres 9,4 z 200 tys. Rzędów i ~ 20 na
customer_id
B. To samo z Postgres 9.5
C. To samo co B., ale z ~ 2,3 wierszami na
customer_id
Powiązane testy porównawcze
Oto nowy test „ogr” z 10 milionami wierszy i 60 tysiącami unikalnych „klientów” na Postgresie 11.5 (aktualny od września 2019). Wyniki są nadal zgodne z tym, co widzieliśmy do tej pory:
Oryginalny (nieaktualny) test porównawczy z 2011 roku
Przeprowadziłem trzy testy z PostgreSQL 9.1 na rzeczywistej tabeli zawierającej 65579 wierszy i indeksach btree w jednej kolumnie dla każdej z trzech zaangażowanych kolumn i najlepszy czas wykonania wynosił 5 uruchomień.
Porównanie pierwszego zapytania @OMGPonies (
A
) z powyższymDISTINCT ON
rozwiązaniem (B
):Wybierz całą tabelę, w tym przypadku powstanie 5958 wierszy.
Użyj warunku, w
WHERE customer BETWEEN x AND y
wyniku którego powstanie 1000 wierszy.Wybierz pojedynczego klienta za pomocą
WHERE customer = x
.Ten sam test powtórzono z indeksem opisanym w drugiej odpowiedzi
źródło
2. row_number()
i5. customer table with LATERAL
, co zapewnia, że identyfikator będzie najmniejszy?customer_id
wiersza o najwyższej wartościtotal
. Jest to mylący przypadek w danych testowych pytania, żeid
w wybranych wierszach zdarza się również, że jest najmniejszy nacustomer_id
.To jest powszechne największa liczba grup na grupęproblem, który ma już dobrze przetestowane i wysoce zoptymalizowane rozwiązania . Osobiście wolę lewe rozwiązanie łączenia autorstwa Billa Karwina ( oryginalny post z wieloma innymi rozwiązaniami ).
Zauważ, że wiele rozwiązań tego powszechnego problemu można zaskakująco znaleźć w jednym z najbardziej oficjalnych źródeł, podręczniku MySQL ! Zobacz przykłady typowych zapytań: Wiersze trzymające grupowo maksimum określonej kolumny .
źródło
DISTINCT ON
wersja jest znacznie krótsza, prostsza i ogólnie działa lepiej w Postgresie niż alternatywy z samodzielnymLEFT JOIN
lub pół-anty-złączeniem zNOT EXISTS
. Jest również „dobrze przetestowany”.W Postgres możesz używać
array_agg
tego w następujący sposób:To da ci
id
największy zakup każdego klienta.Kilka rzeczy do zapamiętania:
array_agg
jest funkcją agregującą, więc działa zGROUP BY
.array_agg
pozwala określić zakres zamówienia tylko do siebie, więc nie ogranicza struktury całego zapytania. Istnieje również składnia sposobu sortowania wartości NULL, jeśli chcesz zrobić coś innego niż domyślny.array_agg
w podobny sposób dla trzeciej kolumny wyników, alemax(total)
jest to prostsze.DISTINCT ON
używania,array_agg
możesz zachować swojeGROUP BY
, na wypadek, gdybyś chciał z innych powodów.źródło
Rozwiązanie to nie jest bardzo wydajne, jak wskazał Erwin, ze względu na obecność SubQ
źródło
Używam w ten sposób (tylko postgresql): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29
Wówczas twój przykład powinien działać prawie tak, jak jest:
CAVEAT: Ignoruje NULL wiersze
Edycja 1 - Zamiast tego użyj rozszerzenia postgres
Teraz używam tego sposobu: http://pgxn.org/dist/first_last_agg/
Aby zainstalować na Ubuntu 14.04:
Jest to rozszerzenie postgres, które daje pierwszą i ostatnią funkcję; najwyraźniej szybszy niż powyższy sposób.
Edycja 2 - Porządkowanie i filtrowanie
Jeśli używasz funkcji agregujących (takich jak te), możesz zamówić wyniki, bez konieczności posiadania danych już zamówionych:
Tak więc równoważny przykład z zamówieniem wyglądałby mniej więcej tak:
Oczywiście możesz porządkować i filtrować według własnego uznania; to bardzo potężna składnia.
źródło
Zapytanie:
JAK TO DZIAŁA! (Byłam tam)
Chcemy mieć pewność, że mamy tylko najwyższą sumę na każdy zakup.
Niektóre rzeczy teoretyczne (pomiń tę część, jeśli chcesz tylko zrozumieć zapytanie)
Niech Total będzie funkcją T (klient, identyfikator), gdzie zwraca wartość podaną nazwą i identyfikatorem Aby udowodnić, że podana suma (T (klient, identyfikator)) jest najwyższa, musimy udowodnić, że chcemy udowodnić
LUB
Pierwsze podejście będzie wymagało od nas zebrania wszystkich rekordów dotyczących tego imienia, które tak naprawdę nie lubię.
Drugi będzie wymagał sprytnego sposobu na stwierdzenie, że rekord nie może być wyższy niż ten.
Powrót do SQL
Jeśli opuścimy, dołącza do tabeli w nazwie i suma jest mniejsza niż połączona tabela:
upewniamy się, że wszystkie rekordy, które mają inny rekord o wyższej sumie dla tego samego użytkownika, zostaną dołączone:
Pomoże nam to przefiltrować najwyższą sumę dla każdego zakupu bez konieczności grupowania:
I takiej odpowiedzi potrzebujemy.
źródło
Bardzo szybkie rozwiązanie
i naprawdę bardzo szybko, jeśli tabela jest indeksowana przez id:
źródło
W SQL Server możesz to zrobić:
Wyjaśnienie: w tym przypadku Grupowanie według odbywa się na podstawie klienta, a następnie zamówienie jest sumowane, następnie każda taka grupa otrzymuje numer seryjny jako StRank i pozyskujemy pierwszego 1 klienta, którego StRank wynosi 1
źródło
Użyj
ARRAY_AGG
funkcji dla PostgreSQL , U-SQL , IBM DB2 i Google BigQuery SQL :źródło
W PostgreSQL inną możliwością jest użycie
first_value
funkcji okna w połączeniu zSELECT DISTINCT
:Utworzyłem kompozyt
(id, total)
, więc obie wartości są zwracane przez ten sam agregat. Oczywiście możesz zawsze złożyć wniosekfirst_value()
dwukrotnie.źródło
Akceptowane rozwiązanie „Obsługiwane przez dowolną bazę danych” firmy OMG Kucyki ma dobrą prędkość z mojego testu.
Tutaj zapewniam to samo podejście, ale bardziej kompletne i czyste rozwiązanie dla dowolnej bazy danych. Wiązania są brane pod uwagę (zakładamy chęć uzyskania tylko jednego wiersza dla każdego klienta, nawet wielu rekordów dla maksymalnej sumy przypadającej na jednego klienta), a inne pola zakupu (np. Id_płatności_kupu) zostaną wybrane dla rzeczywistych pasujących wierszy w tabeli zakupów.
Obsługiwane przez dowolną bazę danych:
To zapytanie jest dość szybkie, zwłaszcza gdy w tabeli zakupów znajduje się indeks złożony, taki jak (klient, ogółem).
Uwaga:
t1, t2 to alias podzapytania, który można usunąć w zależności od bazy danych.
Uwaga :
using (...)
klauzula nie jest obecnie obsługiwana w MS-SQL i Oracle db od tej edycji w styczniu 2017 r. Musisz ją rozwinąć do np.on t2.id = purchase.id
Itp. Składnia USING działa w SQLite, MySQL i PostgreSQL.źródło
Snowflake / Teradata obsługuje
QUALIFY
klauzulę, która działa jakHAVING
dla funkcji okienkowych:źródło
Jeśli chcesz wybrać dowolny (według określonych warunków) wiersz ze zbioru zagregowanych wierszy.
Jeśli chcesz użyć innej (
sum/avg
) funkcji agregującej opróczmax/min
. W związku z tym nie można używać pojęcia zDISTINCT ON
Możesz użyć następnego podkwerendy:
Można zastąpić
amount = MAX( tf.amount )
dowolnym warunkiem z jednym ograniczeniem: to podzapytanie nie może zwracać więcej niż jednego wierszaAle jeśli chcesz robić takie rzeczy, prawdopodobnie szukasz funkcji okna
źródło
W przypadku SQl Server najskuteczniejszym sposobem jest:
i nie zapomnij utworzyć indeksu klastrowego dla używanych kolumn
źródło