Można tu znaleźć wiele podobnych pytań, ale nie sądzę, aby jakakolwiek odpowiedź na to pytanie była odpowiednia.
Będę kontynuować od najpopularniejszego pytania i użyję ich przykładu, jeśli to w porządku.
W tym przypadku zadaniem jest uzyskanie najnowszego postu dla każdego autora w bazie danych.
Przykładowe zapytanie daje bezużyteczne wyniki, ponieważ nie zawsze jest to ostatni zwracany post.
SELECT wp_posts.* FROM wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
ORDER BY wp_posts.post_date DESC
Obecna akceptowana odpowiedź to
SELECT
wp_posts.*
FROM wp_posts
WHERE
wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
HAVING wp_posts.post_date = MAX(wp_posts.post_date) <- ONLY THE LAST POST FOR EACH AUTHOR
ORDER BY wp_posts.post_date DESC
Niestety ta odpowiedź jest prosta i błędna, aw wielu przypadkach daje mniej stabilne wyniki niż pierwotne zapytanie.
Moim najlepszym rozwiązaniem jest użycie podzapytania formularza
SELECT wp_posts.* FROM
(
SELECT *
FROM wp_posts
ORDER BY wp_posts.post_date DESC
) AS wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
Moje pytanie jest więc proste: czy w ogóle można zamówić wiersze przed grupowaniem bez uciekania się do podzapytania?
Edycja : To pytanie było kontynuacją innego pytania, a specyfika mojej sytuacji jest nieco inna. Możesz (i powinieneś) założyć, że istnieje również wp_posts.id, który jest unikalnym identyfikatorem dla tego konkretnego postu.
źródło
post_author
ipost_date
nie wystarczą, aby uzyskać unikalny wiersz, więc musi być więcej, aby uzyskać unikalny wiersz napost_author
There are plenty of similar questions to be found on here but I don't think that any answer the question adequately.
Po to są nagrody.Odpowiedzi:
Korzystanie
ORDER BY
z podzapytania nie jest najlepszym rozwiązaniem tego problemu.Najlepszym rozwiązaniem, aby uzyskać
max(post_date)
autor, jest użycie podzapytania, aby zwrócić maksymalną datę, a następnie dołączyć ją do tabeli zarówno wpost_author
dniu maksymalnym, jak i maksymalnym.Rozwiązaniem powinno być:
Jeśli masz następujące przykładowe dane:
Podkwerenda zwróci maksymalną datę i autora:
Następnie, ponieważ dołączasz to z powrotem do tabeli, w przypadku obu wartości zwrócisz pełne szczegóły tego postu.
Zobacz SQL Fiddle with Demo .
Aby rozwinąć moje komentarze na temat korzystania z podzapytania w celu dokładnego zwrócenia tych danych.
MySQL nie zmusza cię do
GROUP BY
każdej kolumny, którą umieszczasz naSELECT
liście. W rezultacie, jeśli tylkoGROUP BY
jedna kolumna zwróci łącznie 10 kolumn, nie ma gwarancji, że pozostałe wartości kolumn należące do tej,post_author
która zostanie zwrócona. Jeśli kolumna nie znajduje się wGROUP BY
MySQL, wybiera jaką wartość należy zwrócić.Użycie podzapytania z funkcją agregującą zagwarantuje, że poprawny autor i post będą zwracane za każdym razem.
Na marginesie, podczas gdy MySQL pozwala na użycie
ORDER BY
w podzapytaniu i pozwala zastosowaćGROUP BY
do nie każdej kolumny naSELECT
liście, to zachowanie nie jest dozwolone w innych bazach danych, w tym SQL Server.źródło
wp_posts
w obu kolumnach, aby uzyskać pełny wiersz.GROUP BY
tylko jedną kolumnę, nie ma gwarancji, że wartości w pozostałych kolumnach będą konsekwentnie poprawne. Niestety, MySQL pozwala na działanie tego typu WYBORU / GRUPOWANIA, czego nie robią inne produkty. PoORDER BY
drugie , składnia użycia an w podzapytaniu, gdy jest dozwolona w MySQL, nie jest dozwolona w innych produktach bazodanowych, w tym SQL Server. Powinieneś użyć rozwiązania, które zwróci właściwy wynik za każdym razem, gdy zostanie wykonane.INDEX(post_author, post_date)
jest ważny.post_id
swoje wewnętrzne zapytanie, technicznie powinieneś je również pogrupować, co najprawdopodobniej wypaczy Twoje wyniki.Twoje rozwiązanie korzysta z rozszerzenia klauzuli GROUP BY , która pozwala grupować według niektórych pól (w tym przypadku po prostu
post_author
):i wybierz niezagregowane kolumny:
które nie są wymienione w grupie według klauzuli lub które nie są używane w funkcji agregującej (MIN, MAX, COUNT itp.).
Prawidłowe użycie rozszerzenia klauzuli GROUP BY
Jest to przydatne, gdy wszystkie wartości niezagregowanych kolumn są równe dla każdego wiersza.
Załóżmy na przykład, że masz stół
GardensFlowers
(name
w ogrodzie,flower
który rośnie w ogrodzie):i chcesz wydobyć wszystkie kwiaty, które rosną w ogrodzie, w którym rośnie wiele kwiatów. Następnie musisz użyć podzapytania, na przykład możesz użyć tego:
Jeśli zamiast tego musisz wyodrębnić wszystkie kwiaty, które są jedynymi kwiatami w garderobie, możesz po prostu zmienić warunek HAVING na
HAVING COUNT(DISTINCT flower)=1
, ale MySql pozwala również na użycie tego:bez podkwerendy, niestandardowy SQL, ale prostszy.
Niepoprawne użycie rozszerzenia klauzuli GROUP BY
Ale co się stanie, jeśli wybierzesz niezagregowane kolumny, które nie są równe dla każdego wiersza? Jaką wartość wybiera MySql dla tej kolumny?
Wygląda na to, że MySql zawsze wybiera PIERWSZĄ wartość, na jaką napotyka.
Aby upewnić się, że pierwsza napotkana wartość jest dokładnie taką, jakiej potrzebujesz, musisz zastosować a
GROUP BY
do uporządkowanego zapytania, stąd potrzeba użycia podzapytania. Nie możesz tego zrobić inaczej.Zakładając, że MySql zawsze wybiera pierwszy napotkany wiersz, poprawnie sortujesz wiersze przed GROUP BY. Ale niestety, jeśli dokładnie przeczytasz dokumentację, zauważysz, że to założenie nie jest prawdziwe.
Podczas wybierania niezagregowanych kolumn, które nie zawsze są takie same, MySql może wybrać dowolną wartość, więc wynikowa wartość, którą faktycznie pokazuje, jest nieokreślona .
Widzę, że ta sztuczka polegająca na uzyskaniu pierwszej wartości niezagregowanej kolumny jest często używana i zwykle / prawie zawsze działa, czasem jej używam (na własne ryzyko). Ale ponieważ nie jest to udokumentowane, nie możesz polegać na tym zachowaniu.
Ten link (dzięki ypercube!) Sztuczka GROUP BY została zoptymalizowana, pokazuje sytuację, w której to samo zapytanie zwraca różne wyniki między MySql a MariaDB, prawdopodobnie z powodu innego silnika optymalizacji.
Więc jeśli ta sztuczka się sprawdzi, to tylko kwestia szczęścia.
Akceptowane odpowiedź na inne pytanie wygląda źle do mnie:
wp_posts.post_date
jest niezagregowaną kolumną, a jej wartość zostanie oficjalnie nieokreślona, ale prawdopodobnie będzie to pierwszapost_date
napotkana. Ale ponieważ sztuczka GROUP BY jest stosowana do nieuporządkowanej tabeli, nie jest pewne, która jest pierwszapost_date
napotkana.Prawdopodobnie zwróci posty, które są jedynymi postami jednego autora, ale nawet to nie zawsze jest pewne.
Możliwe rozwiązanie
Myślę, że to może być możliwe rozwiązanie:
Na wewnętrzne zapytanie zwracam maksymalną datę publikacji dla każdego autora. Następnie biorę pod uwagę fakt, że ten sam autor może teoretycznie mieć dwa posty w tym samym czasie, więc otrzymuję tylko maksymalny identyfikator. A potem zwracam wszystkie wiersze, które mają te maksymalne identyfikatory. Można to zrobić szybciej, używając sprzężeń zamiast klauzuli IN.
(Jeśli jesteś pewien, że
ID
to tylko rośnie, a jeśliID1 > ID2
to oznaczapost_date1 > post_date2
, to zapytanie może być znacznie prostsze, ale nie jestem pewien, czy tak jest).źródło
extension to GROUP By
ciekawa lektura, dzięki za to.To, co zamierzasz przeczytać, jest raczej hakerskie, więc nie próbuj tego w domu!
Ogólnie w SQL odpowiedź na twoje pytanie brzmi NIE , ale ze względu na zrelaksowany tryb
GROUP BY
(wspomniany przez @bluefeet ) odpowiedź w MySQL brzmi TAK .Załóżmy, że masz indeks BTREE na (post_status, post_type, post_author, post_date). Jak wygląda indeks pod maską?
(post_status = 'opublikuj', post_type = 'post', post_author = 'użytkownik A', post_date = '2012-12-01') (post_status = 'opublikuj', post_type = 'post', post_author = 'użytkownik A', post_date = '2012-12-31') (post_status = 'opublikuj', post_type = 'post', post_author = 'użytkownik B', post_date = '2012-10-01') (post_status = 'opublikuj', post_type = ' post ”, post_author =„ użytkownik B ”, post_date =„ 2012-12-01 ”)
Oznacza to, że dane są sortowane według wszystkich pól w porządku rosnącym.
Podczas wykonywania
GROUP BY
domyślnie sortuje dane według pola grupującego (post_author
w naszym przypadku; post_status, typ_typu są wymagane przezWHERE
klauzulę), a jeśli istnieje zgodny indeks, pobiera dane dla każdego pierwszego rekordu w porządku rosnącym. To jest zapytanie pobierze następujące (pierwszy post dla każdego użytkownika):(post_status = 'opublikuj', post_type = 'post', post_author = 'użytkownik A', post_date = '2012-12-01') (post_status = 'opublikuj', post_type = 'post', post_author = 'użytkownik B', post_date = „2012-10-01”)
Ale
GROUP BY
w MySQL pozwala jawnie określić kolejność. A kiedy poprosiszpost_user
w kolejności malejącej, przejdzie on przez nasz indeks w odwrotnej kolejności, wciąż biorąc pierwszy rekord dla każdej grupy, która jest rzeczywiście ostatnia.To jest
da nam
(post_status = 'opublikuj', post_type = 'post', post_author = 'użytkownik B', post_date = '2012-12-01') (post_status = 'opublikuj', post_type = 'post', post_author = 'użytkownik A', post_date = „2012-12-31”)
Teraz, kiedy zamawiasz wyniki grupowania według post_date, otrzymujesz pożądane dane.
NB :
Nie to poleciłbym dla tego konkretnego zapytania. W takim przypadku użyłbym nieco zmodyfikowanej wersji sugerowanej przez @bluefeet . Ale ta technika może być bardzo przydatna. Spójrz na moją odpowiedź tutaj: Pobieranie ostatniego rekordu w każdej grupie
Pułapki : wady tego podejścia
Zaletą jest wydajność w trudnych przypadkach. W takim przypadku wydajność zapytania powinna być taka sama, jak w zapytaniu @ bluefeet, ze względu na ilość danych zaangażowanych w sortowanie (wszystkie dane są ładowane do tabeli tymczasowej, a następnie sortowane; przy okazji jego zapytanie również wymaga
(post_status, post_type, post_author, post_date)
indeksu) .Co sugerowałbym :
Jak powiedziałem, zapytania te powodują, że MySQL marnuje czas na sortowanie potencjalnie dużych ilości danych w tabeli tymczasowej. W przypadku, gdy potrzebujesz stronicowania (dotyczy to LIMIT), większość danych jest nawet odrzucana. Chciałbym zminimalizować ilość posortowanych danych: to posortować i ograniczyć minimum danych w podzapytaniu, a następnie dołączyć ponownie do całej tabeli.
To samo zapytanie przy użyciu podejścia opisanego powyżej:
Wszystkie te zapytania wraz z ich planami wykonania na SQLFiddle .
źródło
Spróbuj tego. Wystarczy pobrać listę najnowszych dat postów od każdego autora . Otóż to
źródło
post_date IN (select max(...) ...)
. Jest to bardziej wydajne niż robienie grupy w podselekcjiIN ( SELECT ... )
jest znacznie mniej wydajny niż równoważny JOIN.Nie. Nie ma sensu porządkowanie rekordów przed grupowaniem, ponieważ grupowanie spowoduje mutację zestawu wyników. Preferowana jest metoda zapytania częściowego. Jeśli to idzie zbyt wolno, musiałbyś zmienić projekt tabeli, na przykład przechowując identyfikator ostatniego postu dla każdego autora w osobnej tabeli lub wprowadzić kolumnę logiczną wskazującą dla każdego autora, który z jego postów jest ostatnim jeden.
źródło
Wystarczy użyć funkcji max i funkcji grupy
źródło
Podsumowując, standardowe rozwiązanie wykorzystuje nieskorelowane podzapytanie i wygląda następująco:
Jeśli używasz starożytnej wersji MySQL lub dość małego zestawu danych, możesz użyć następującej metody:
źródło
** Zapytania podrzędne mogą mieć zły wpływ na wydajność, jeśli są używane z dużymi zestawami danych **
Oryginalne zapytanie
Zmodyfikowane zapytanie
ponieważ używam
max
wselect clause
==>max(p.post_date)
możliwe jest uniknięcie zapytań subselekcji i uporządkowanie według maksymalnej kolumny po grupie według.źródło
Po pierwsze, nie używaj * w select, wpływa to na ich wydajność i utrudnia korzystanie z grupy według i sortowanie według. Spróbuj tego zapytania:
Jeśli nie określisz tabeli w ORDER BY, tylko alias, uporządkują wynik zaznaczenia.
źródło