Jak uzyskać wiersze zawierające maksymalną wartość dla każdego zgrupowanego zestawu?
Widziałem pewne zbyt skomplikowane warianty tego pytania i żadne z dobrą odpowiedzią. Próbowałem stworzyć najprostszy możliwy przykład:
Biorąc pod uwagę poniższą tabelę z kolumnami osoby, grupy i wieku, w jaki sposób uzyskasz najstarszą osobę w każdej grupie? (Remis w grupie powinien dać pierwszy wynik alfabetyczny)
Person | Group | Age
---
Bob | 1 | 32
Jill | 1 | 34
Shawn| 1 | 42
Jake | 2 | 29
Paul | 2 | 36
Laura| 2 | 39
Pożądany zestaw wyników:
Shawn | 1 | 42
Laura | 2 | 39
mysql
sql
greatest-n-per-group
Yarin
źródło
źródło
Odpowiedzi:
Jest bardzo prosty sposób, aby to zrobić w mysql:
Działa to, ponieważ w mysql nie można agregować kolumn nie grupujących według, w którym to przypadku mysql zwraca tylko pierwszy wiersz. Rozwiązaniem jest takie uporządkowanie danych, aby dla każdej grupy najpierw był wiersz, który chcesz, a następnie grupowanie według kolumn, dla których chcesz uzyskać wartość.
Unikasz skomplikowanych podzapytań, które próbują znaleźć
max()
itp., A także problemów ze zwracaniem wielu wierszy, gdy jest więcej niż jeden o tej samej wartości maksymalnej (tak jak zrobiłyby to inne odpowiedzi)Uwaga: jest to rozwiązanie tylko dla MySQL . Wszystkie inne bazy danych, które znam, wyrzucą błąd składniowy SQL z komunikatem „kolumny nie zagregowane nie są wymienione w grupie według klauzul” lub podobne. Ponieważ to rozwiązanie wykorzystuje nieudokumentowane zachowanie, bardziej ostrożni mogą chcieć dołączyć test, aby stwierdzić, że nadal działa, jeśli przyszła wersja MySQL zmieni to zachowanie.
Aktualizacja wersji 5.7:
Od wersji 5.7
sql-mode
ustawienie zawieraONLY_FULL_GROUP_BY
domyślnie, więc aby to zadziałało, nie możesz mieć tej opcji (edytuj plik opcji dla serwera, aby usunąć to ustawienie).źródło
SELECT
klauzuli i nie są obliczane przy użyciu funkcji agregującej.SELECT
klauzuli nie są funkcjonalnie zależne odGROUP BY
kolumn. Jeśli jest skonfigurowany do akceptowania go (`ONLY_FULL_GROUP_BY` jest wyłączony), działa jak poprzednie wersje (tzn. Wartości tych kolumn są nieokreślone).GROUP BY
kondensuje się do jednego rekordu, ale wszystkie pola zostaną arbitralnie wybrane z rekordów. Być może MySQL obecnie po prostu zawsze wybiera pierwszy wiersz, ale równie dobrze może wybrać dowolny inny wiersz, a nawet wartości z różnych wierszy w przyszłej wersji.Prawidłowe rozwiązanie to:
Jak to działa:
Pasuje do każdego wiersza
o
ze wszystkimi wierszamib
o tej samej wartości w kolumnieGroup
i większej wartości w kolumnieAge
. Każdy wiersz, któryo
nie ma maksymalnej wartości swojej grupy w kolumnie,Age
będzie pasował do jednego lub więcej wierszy zb
.LEFT JOIN
Sprawia, że pasuje najstarszą osobą w grupie (w tym osoby, które są same w grupie) z rzędu pełnejNULL
szb
( „bez największego wieku w grupie”).Użycie
INNER JOIN
powoduje, że te wiersze nie pasują i są ignorowane.WHERE
Klauzula utrzymuje tylko wiersze oNULL
sw pól wydobytych zb
. Są to najstarsze osoby z każdej grupy.Dalsze odczyty
To i wiele innych rozwiązań wyjaśniono w książce SQL Antipatterns: Unikanie pułapek programowania baz danych
źródło
o.Age = b.Age
np. Jeśli Paul z grupy 2 ma 39, jak Laura. Jeśli jednak nie chcemy takiego zachowania, możemy:ON o.Group = b.Group AND (o.Age < b.Age or (o.Age = b.Age and o.id < b.id))
Możesz dołączyć do podzapytania, które ściąga
MAX(Group)
iAge
. Ta metoda jest przenośna w większości RDBMS.źródło
Group = 2, Age = 20
podzapytanie zwróciłoby jeden z nich, aleON
klauzula łączenia pasowałaby do obu z nich, więc otrzymalibyśmy 2 wiersze z tą samą grupą / wiekiem, ale różne wartości dla innych kolumn, zamiast jednego.Moje proste rozwiązanie dla SQLite (i prawdopodobnie MySQL):
Jednak nie działa w PostgreSQL i może na niektórych innych platformach.
W PostgreSQL możesz użyć klauzuli DISTINCT ON :
źródło
Przy użyciu metody rankingu.
źródło
:=
- co to jest?Nie jestem pewien, czy MySQL ma funkcję numer_wiersza. Jeśli tak, możesz go użyć, aby uzyskać pożądany rezultat. Na SQL Server możesz zrobić coś podobnego do:
źródło
Ostatecznie rozwiązanie Axiac było dla mnie najlepsze. Miałem jednak dodatkową złożoność: obliczoną „wartość maksymalną”, uzyskaną z dwóch kolumn.
Użyjmy tego samego przykładu: chciałbym, aby najstarsza osoba w każdej grupie. Jeśli są ludzie w tym samym wieku, weź najwyższą osobę.
Musiałem wykonać lewe połączenie dwa razy, aby uzyskać takie zachowanie:
Mam nadzieję że to pomoże! Myślę jednak, że powinien być lepszy sposób na zrobienie tego ...
źródło
Moje rozwiązanie działa tylko wtedy, gdy potrzebujesz pobrać tylko jedną kolumnę, jednak dla moich potrzeb było to najlepsze rozwiązanie znalezione pod względem wydajności (używa tylko jednego zapytania!):
Używa GROUP_CONCAT, aby utworzyć uporządkowaną listę konkat, a następnie podciąć tylko do pierwszej.
źródło
Mam proste rozwiązanie, używając
WHERE IN
źródło
Korzystanie z CTE - typowe wyrażenia tabelowe:
źródło
W Oracle poniżej zapytanie może dać pożądany wynik.
źródło
źródło
Możesz także spróbować
źródło
Nie użyłbym grupy jako nazwy kolumny, ponieważ jest to słowo zastrzeżone. Jednak następujące SQL działałoby.
źródło
Zaletą tej metody jest możliwość pozycjonowania według innej kolumny i nie niszczenie innych danych. Jest to bardzo przydatne w sytuacji, gdy próbujesz wyświetlić listę zamówień z kolumną zawierającą pozycje, na początku najcięższą.
Źródło: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
źródło
niech nazwa stołu to ludzie
źródło
Jeśli potrzebne jest ID (i wszystkie coulmns) z mytable
źródło
W ten sposób otrzymuję N maks wierszy na grupę w mysql
jak to działa:
co.country = ci.country
) < 1
to dla 3 elementów -) <3co.id < ci.id
Pełny przykład tutaj:
mysql wybierz n maks. wartości na grupę
źródło