Istnieje tabela, messages
która zawiera dane, jak pokazano poniżej:
Id Name Other_Columns
-------------------------
1 A A_data_1
2 A A_data_2
3 A A_data_3
4 B B_data_1
5 B B_data_2
6 C C_data_1
Jeśli uruchomię zapytanie select * from messages group by name
, otrzymam wynik w postaci:
1 A A_data_1
4 B B_data_1
6 C C_data_1
Jakie zapytanie zwróci następujący wynik?
3 A A_data_3
5 B B_data_2
6 C C_data_1
Oznacza to, że należy zwrócić ostatni rekord w każdej grupie.
Obecnie używam tego zapytania:
SELECT
*
FROM (SELECT
*
FROM messages
ORDER BY id DESC) AS x
GROUP BY name
Ale to wygląda bardzo nieefektywnie. Jakieś inne sposoby na osiągnięcie tego samego rezultatu?
sql
mysql
group-by
greatest-n-per-group
Vijay Dev
źródło
źródło
Odpowiedzi:
MySQL 8.0 obsługuje teraz funkcje okienkowania, jak prawie wszystkie popularne implementacje SQL. Dzięki tej standardowej składni możemy pisać zapytania typu „n-na-grupę”:
Poniżej znajduje się oryginalna odpowiedź, którą napisałem na to pytanie w 2009 r .:
Piszę rozwiązanie w ten sposób:
Jeśli chodzi o wydajność, jedno lub drugie rozwiązanie może być lepsze, w zależności od charakteru danych. Powinieneś więc przetestować oba zapytania i użyć tego, które jest lepsze pod względem wydajności, biorąc pod uwagę bazę danych.
Na przykład mam kopię zrzutu danych StackOverflow August . Użyję tego do testów porównawczych. Tabela zawiera 1114357 wierszy
Posts
. Działa to na MySQL 5.0.75 na moim Macbooku Pro 2.40GHz.Napiszę zapytanie, aby znaleźć najnowszy post dla danego identyfikatora użytkownika (mojego).
Najpierw użyj techniki pokazanej przez @Eric z
GROUP BY
podzapytaniem:Nawet
EXPLAIN
analiza zajmuje ponad 16 sekund:Teraz wygeneruj ten sam wynik zapytania przy użyciu mojej techniki z
LEFT JOIN
:Do
EXPLAIN
analizy wynika, że obie tabele są w stanie używać ich indeksy:Oto DDL dla mojej
Posts
tabeli:źródło
<=
nie pomoże, jeśli masz nieunikalną kolumnę. Musisz użyć unikalnej kolumny jako remisu.UPD: 31.03.2017, wersja 5.7.5 MySQL włącza domyślnie przełącznik ONLY_FULL_GROUP_BY (dlatego niedeterministyczne zapytania GROUP BY zostały wyłączone). Ponadto zaktualizowali implementację GROUP BY i rozwiązanie może już nie działać zgodnie z oczekiwaniami, nawet przy wyłączonym przełączniku. Trzeba to sprawdzić.
Powyższe rozwiązanie Billa Karwina działa dobrze, gdy liczba elementów w grupach jest raczej mała, ale wydajność zapytania staje się zła, gdy grupy są dość duże, ponieważ rozwiązanie wymaga
n*n/2 + n/2
tylko okołoIS NULL
porównań.Testy wykonałem na tabeli
18684446
wierszy InnoDB z1182
grupami. Tabela zawiera wyniki testów dla testów funkcjonalnych i ma(test_id, request_id)
jako klucz podstawowy. Tak więctest_id
jest grupa i szukałem ostatniegorequest_id
dla każdegotest_id
.Rozwiązanie Billa działa już od kilku godzin na moim telefonie Dell E4310 i nie wiem, kiedy to się skończy, mimo że działa na indeksie zasięgu (stąd
using index
w EXPLAIN).Mam kilka innych rozwiązań opartych na tych samych pomysłach:
(group_id, item_value)
parą jest ostatnia z nichgroup_id
, czyli pierwsza dla każdegogroup_id
jeśli przejdziemy przez indeks w kolejności malejącej;3 sposoby, w jakie MySQL używa indeksów, to świetny artykuł, aby zrozumieć niektóre szczegóły.
Rozwiązanie 1
Ten jest niesamowicie szybki, zajmuje mi około 0,8 sekundy w moich rzędach ponad 18 milionów:
Jeśli chcesz zmienić kolejność na ASC, umieść ją w podzapytaniu, zwróć tylko identyfikatory i użyj tego jako podzapytania, aby dołączyć do reszty kolumn:
To zajmuje około 1,2 sekundy moich danych.
Rozwiązanie 2
Oto inne rozwiązanie, które zajmuje około 19 sekund dla mojego stołu:
Zwraca również testy w kolejności malejącej. Jest o wiele wolniejszy, ponieważ wykonuje pełne skanowanie indeksu, ale jest tutaj, aby dać ci wyobrażenie, jak wyprowadzać N max wierszy dla każdej grupy.
Wadą zapytania jest to, że jego wynik nie może być buforowany przez pamięć podręczną zapytania.
źródło
SELECT test_id, request_id FROM testresults GROUP BY test_id;
zwraca minimalny identyfikator żądania dla każdego identyfikatora testu.Użyj swojego podzapytania aby zwrócić prawidłowe grupowanie, ponieważ jesteś w połowie drogi.
Spróbuj tego:
Jeśli nie
id
, chcesz maksymalnie:W ten sposób unikasz skorelowanych podkwerend i / lub porządkowania w swoich podkwerendach, które zwykle są bardzo wolne / nieefektywne.
źródło
other_col
: jeśli ta kolumna nie jest unikalna, możesz odzyskać wiele rekordów z tym samymname
, jeśli są one powiązanemax(other_col)
. Znalazłem ten post, który opisuje rozwiązanie dla moich potrzeb, w którym potrzebuję dokładnie jednego rekordu naname
.INDEX(name, id)
iINDEX(name, other_col)
Doszedłem do innego rozwiązania, które polega na uzyskaniu identyfikatorów ostatniego postu w każdej grupie, a następnie wybranie z tabeli komunikatów przy użyciu wyniku z pierwszego zapytania jako argumentu dla
WHERE x IN
konstrukcji:Nie wiem, jak to działa w porównaniu z niektórymi innymi rozwiązaniami, ale zadziwiająco zadziałało na moim stole z ponad 3 milionami wierszy. (4 sekundy wykonania z wynikami ponad 1200)
Powinno to działać zarówno na MySQL, jak i SQL Server.
źródło
Rozwiązanie przez sub kwerendę Fiddle Link
Rozwiązanie Łącząc warunek łącze skrzypce
Powodem tego postu jest podanie tylko linku do skrzypiec. Ten sam kod SQL jest już podany w innych odpowiedziach.
źródło
Podejście ze znaczną prędkością jest następujące.
Wynik
źródło
id
jest uporządkowane tak, jak potrzebujesz. W ogólnym przypadku potrzebna jest inna kolumna.Oto dwie sugestie. Po pierwsze, jeśli mysql obsługuje ROW_NUMBER (), jest to bardzo proste:
Zakładam, że przez „ostatni” masz na myśli ostatni w kolejności id. Jeśli nie, zmień odpowiednio klauzulę ORDER BY w oknie ROW_NUMBER (). Jeśli ROW_NUMBER () nie jest dostępne, jest to inne rozwiązanie:
Po drugie, jeśli nie, jest to często dobry sposób na kontynuację:
Innymi słowy, wybierz wiadomości, w których nie ma wiadomości z późniejszym identyfikatorem o tej samej nazwie.
źródło
ROW_NUMBER()
i CTE.Nie testowałem jeszcze z dużą DB, ale myślę, że może to być szybsze niż dołączanie do tabel:
źródło
Oto inny sposób na uzyskanie ostatniego powiązanego rekordu za
GROUP_CONCAT
pomocą kolejności według iSUBSTRING_INDEX
wybranie jednego z rekordów z listyPowyższe zapytanie grupuje wszystkie,
Other_Columns
które są w tej samejName
grupie, a użycieORDER BY id DESC
łączy wszystkieOther_Columns
w określonej grupie w malejącej kolejności z dostarczonym separatorem w moim przypadku, którego użyłem||
, używającSUBSTRING_INDEX
tej listy spowoduje wybranie pierwszegoFiddle Demo
źródło
group_concat_max_len
ogranicza to liczbę wierszy, które możesz obsłużyć.Oczywiste jest, że istnieje wiele różnych sposobów uzyskiwania takich samych wyników, wydaje się, że Twoim pytaniem jest skuteczny sposób uzyskania ostatnich wyników w każdej grupie w MySQL. Jeśli pracujesz z ogromną ilością danych i zakładasz, że używasz InnoDB nawet z najnowszymi wersjami MySQL (takimi jak 5.7.21 i 8.0.4-rc), może nie być skutecznego sposobu na zrobienie tego.
Czasami musimy to zrobić w przypadku tabel zawierających nawet ponad 60 milionów wierszy.
W tych przykładach wykorzystam dane zawierające tylko około 1,5 miliona wierszy, w których zapytania będą musiały znaleźć wyniki dla wszystkich grup w danych. W naszych rzeczywistych przypadkach często musielibyśmy zwrócić dane z około 2000 grup (co hipotetycznie nie wymagałoby badania bardzo dużej ilości danych).
Użyję następujących tabel:
Tabela temperatur zawiera około 1,5 miliona losowych rekordów i 100 różnych grup. Grupa selected_group jest wypełniona tymi 100 grupami (w naszych przypadkach byłoby to zwykle mniej niż 20% dla wszystkich grup).
Ponieważ dane te są losowe, oznacza to, że wiele wierszy może mieć takie same zarejestrowane znaczniki czasu. Chcemy uzyskać listę wszystkich wybranych grup w kolejności groupID z ostatnim zarejestrowanym znacznikiem czasu dla każdej grupy, a jeśli ta sama grupa ma więcej niż jeden pasujący wiersz, to ostatni pasujący identyfikator tych wierszy.
Gdyby hipotetycznie MySQL miał funkcję last (), która zwracała wartości z ostatniego wiersza w specjalnej klauzuli ORDER BY, moglibyśmy po prostu zrobić:
który w tym przypadku musiałby zbadać tylko kilka 100 wierszy, ponieważ nie używa żadnej z normalnych funkcji GROUP BY. Wykonałoby się to w 0 sekund, a zatem byłoby bardzo wydajne. Zauważ, że normalnie w MySQL zobaczylibyśmy klauzulę ORDER BY następującą po klauzuli GROUP BY, jednak ta klauzula ORDER BY służy do określenia ORDER dla funkcji last (), gdyby była po GROUP BY, wówczas zamawiałaby GRUPY. Jeśli nie ma klauzuli GROUP BY, ostatnie wartości będą takie same we wszystkich zwróconych wierszach.
Jednak MySQL tego nie ma, więc przyjrzyjmy się różnym pomysłom na to, co ma i udowodnij, że żadne z nich nie jest wydajne.
Przykład 1
Przebadano 3 009 254 wierszy i zajęło ~ 0,859 sekund na 5.7.21 i nieco dłużej na 8.0.4-rc
Przykład 2
Przebadano 15050331 rzędów i zajęło ~ 1,25 sekundy na 5.7.21 i nieco dłużej na 8.0.4-rc
Przykład 3
Przebadano 3 009 685 wierszy i zajęło ~ 1,95 sekundy na 57,21 i nieco dłużej na 8,0.4-rc
Przykład 4
Przebadano 6 137 810 wierszy i zajęło ~ 2,2 sekundy na 57,21 i nieco dłużej na 8,0.4-rc
Przykład 5
To zbadało 6017808 rzędów i zajęło ~ 4,2 sekundy na 8.0.4-rc
Przykład 6
To zbadało 6017908 rzędów i zajęło ~ 17,5 sekundy na 8.0.4-rc
Przykład 7
Ten trwał wiecznie, więc musiałem go zabić.
źródło
SELECT DISTINCT(groupID)
jest szybki i da ci wszystkie dane potrzebne do zbudowania takiego zapytania. Rozmiar zapytania powinien być w porządku, o ile nie przekracza onmax_allowed_packet
, co domyślnie wynosi 4 MB w MySQL 5.7.przyjrzymy się, jak możesz użyć MySQL do uzyskania ostatniego rekordu w grupie według rekordów. Na przykład, jeśli masz ten zestaw wyników postów.
id category_id post_title
1 1 Title 1
2 1 Title 2
3 1 Title 3
4 2 Title 4
5 2 Title 5
6 3 Title 6
Chcę być w stanie uzyskać ostatni post w każdej kategorii, którą są tytuł 3, tytuł 5 i tytuł 6. Aby uzyskać posty według kategorii, będziesz używać klawiatury MySQL Group By.
select * from posts group by category_id
Ale wyniki, które otrzymujemy z tego zapytania, są.
id category_id post_title
1 1 Title 1
4 2 Title 4
6 3 Title 6
Grupa według zawsze zwróci pierwszy rekord w grupie z zestawu wyników.
SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );
Spowoduje to zwrócenie postów o najwyższych identyfikatorach w każdej grupie.
id category_id post_title
3 1 Title 3
5 2 Title 5
6 3 Title 6
Odniesienie Kliknij tutaj
źródło
źródło
Oto moje rozwiązanie:
źródło
SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
.Spróbuj tego:
źródło
Cześć @Vijay Dev, jeśli wiadomości w tabeli zawierają identyfikator, który jest kluczem podstawowym automatycznego przyrostu, to aby pobrać najnowszą bazę rekordów na kluczu podstawowym, zapytanie powinno brzmieć jak poniżej:
źródło
Możesz także zobaczyć widok z tego miejsca.
http://sqlfiddle.com/#!9/ef42b/9
PIERWSZE ROZWIĄZANIE
DRUGIE ROZWIĄZANIE
źródło
źródło
**
Cześć, to zapytanie może pomóc:
**
źródło
Czy jest jakiś sposób, aby użyć tej metody do usuwania duplikatów w tabeli? Zestaw wyników jest w zasadzie zbiorem unikatowych rekordów, więc jeśli moglibyśmy usunąć wszystkie rekordy spoza zestawu wyników, nie mielibyśmy duplikatów? Próbowałem tego, ale mySQL dał błąd 1093.
Czy istnieje sposób, aby zapisać dane wyjściowe w zmiennej temp, a następnie usunąć z NOT IN (zmienna temp)? @Bill dzięki za bardzo przydatne rozwiązanie.
EDYCJA: Myślę, że znalazłem rozwiązanie:
źródło
Poniższe zapytanie będzie działało poprawnie zgodnie z Twoim pytaniem.
źródło
Jeśli chcesz mieć ostatni wiersz dla każdego
Name
, możesz podać numer wiersza każdej grupie wierszy wedługName
i uporządkować wedługId
malejącej kolejności.PYTANIE
SQL Fiddle
źródło
Co powiesz na to:
Miałem podobny problem (trudny postgresql) i tabelę rekordów 1M. To rozwiązanie zajmuje 1,7 w porównaniu do 44 wyprodukowanych przez tę z LEFT JOIN. W moim przypadku musiałem przefiltrować odpowiedni parametr Twojego pola nazwy względem wartości NULL, co skutkuje jeszcze lepszą wydajnością o 0,2 sekundy
źródło
Jeśli naprawdę zależy Ci na wydajności, możesz wprowadzić nową kolumnę w tabeli o nazwie
IsLastInGroup
typu BIT.Ustaw wartość true w kolumnach, które są ostatnie i zachowaj ją przy każdym wstawianiu / aktualizacji / usuwaniu wiersza. Pisanie będzie wolniejsze, ale zyskasz na czytaniach. To zależy od twojego przypadku użycia i polecam tylko, jeśli jesteś skoncentrowany na czytaniu.
Twoje zapytanie będzie wyglądać następująco:
źródło
źródło
Możesz grupować, licząc, a także uzyskać ostatni element grupy, taki jak:
źródło
Nadzieja poniżej zapytania Oracle może pomóc:
źródło
Inne podejście:
Znajdź właściwość o maksymalnej cenie m2_z każdym programem (n właściwości w 1 programie):
źródło