Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Problem z sortowaniem przed grupowaniem
Forum PHP.pl > Forum > Bazy danych > MySQL
menda90
Witam, wiem że temat był dużo razy poruszany ale nadal nie potrafię zastosować go do swoich potrzeb.
Mam tabelę:
  1. CREATE TABLE IF NOT EXISTS `wiadomosci` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `grupa` int(11) NOT NULL,
  4. `wiadomosc` varchar(2048) CHARACTER SET utf8 COLLATE utf8_polish_ci NOT NULL,
  5. `from_id` int(11) NOT NULL,
  6. `to_id` int(11) NOT NULL,
  7. PRIMARY KEY (`id`)
  8. );
  9.  
  10.  
  11. INSERT INTO `wiadomosci` (id, grupa, wiadomosc, from_id, to_id) VALUES (`1`, `1`, `Witam`,`20`,`30`);
  12. INSERT INTO `wiadomosci` (id, grupa, wiadomosc, from_id, to_id) VALUES (`2`, `1`, `Witam2`,`20`,`30`);
  13. INSERT INTO `wiadomosci` (id, grupa, wiadomosc, from_id, to_id) VALUES (`3`, `2`, `Witam3`,`22`,`30`);
  14. INSERT INTO `wiadomosci` (id, grupa, wiadomosc, from_id, to_id) VALUES (`4`, `2`, `Witam4`,`30`,`22`);
  15. INSERT INTO `wiadomosci` (id, grupa, wiadomosc, from_id, to_id) VALUES (`5`, `2`, `Witam5`,`22`,`30`);


i chcę wyciągnąć najświeższą wiadomość dla id uzytkownika="30" , gdzie grupowanie jest po polu "grupa", a najświeższa wiadomość to ta, która ma największe "id".

  1. SELECT wiadomosc FROM wiadomosci WHERE from_id=30 OR to_id=30 GROUP BY grupa ORDER BY id DESC

wyciąga mi
  1. Witam
  2. Witam3

a potrzebne
  1. Witam2
  2. Witam5

jakiś pomysł?
pmir13
Dlaczego potrzebujesz grupowania?

Używanie w select nazw kolumn, które nie są objęte ani funkcją agregującą ani nie są w group by jest dozwolone w mysql, ze względów wydajnościowych, ale nie jest to poprawna składnia tradycyjnego SQL. Na przykład próbując takie samo zapytanie w MSSQL dostałbyś znany chyba wszystkim, którzy z sql serverem mieli do czynienia błąd:
Column 'wiadomosc' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

Mysql pozwala na takie rzeczy, takie kolumny nazywając ukryte (hidden), ale wartość, którą zwraca select jest pewna tylko jeśli wszystkie wiersze w obrębie tej samej grupy mają ją identyczną.
http://dev.mysql.com/doc/refman/5.6/en/gro...en-columns.html

W twoim przypadku, ponieważ kolumna wiadomosc nie występuje w group by (tam jest tylko grupa), nie wykonujesz też na tej wiadomości nic takiego jak sumowanie, zliczanie, czy cokolwiek z listy funkcji agregujących http://dev.mysql.com/doc/refman/5.6/en/gro...-functions.html, nie możesz mieć absolutnie żadnej pewności z którego rekordu mysql zwróci wartość kolumny wiadomosc.

Żeby stwierdzić jak dokładnie musisz zbudować zapytanie musisz najpierw precyzyjnie odpowiedzieć na pierwsze pytanie.
thek
Mysql w takich wypadkach jak podałeś zwraca wartość kolumny dla pierwszego wiersza danej grupy. To co chcesz więc osiągnąć, to ustawić wiersze we własciwej kolejności zanim dojdze do grupowania. Jako że ORDER BY wykonuje się dopiero po GROUP BY, to musisz do grupowania wysłać podzapytanie z posortowanymi wynikami. W tym wypadku musisz więc "odwrócić" tabelę w podzapytaniu i potem ją pogrupować.

A co do celu, to chyba jest taki, by użytkownik widział najświeższe wiadomości związane z sobą (stąd sprawdzanie from_id i to_id) w każdej z grup.
pmir13
Cytat(thek @ 28.04.2011, 09:49:56 ) *
Mysql w takich wypadkach jak podałeś zwraca wartość kolumny dla pierwszego wiersza danej grupy. To co chcesz więc osiągnąć, to ustawić wiersze we własciwej kolejności zanim dojdze do grupowania.


Przecież to kompletnie przeczy temu co można przeczytać w manualu mysqla, do którego link podałem. I to dokładny link, omawiający właśnie taką sytuację. Naprawdę tak trudno sprawdzić?

Można mówić - u mnie na moim systemie, na mojej wersji mysql, teraz zwracany jest pierwszy wiersz z grupy. Nic więcej.

http://bugs.mysql.com/bug.php?id=60735
Tutaj mamy bardzo konkretny przykład - ktoś był przekonany o tym, że właśnie pierwszy wiersz powinien być zawsze zwracany, oparł na tym założeniu swój system, robiąc dokładnie to co proponujesz, najpierw sortował, a potem grupował, po czym jakież było jego zdziwienie, gdy po przejściu na inną wersję okazało się, że zwracany jest ostatni wiersz zamiast pierwszego. Zgłosił więc bug, ale dostał bardzo krótką odpowiedź, odsyłającą do wyżej wspomnianego linku.
thek
pmir: To że przeczy dokumentacji, nie znaczy że MySQL tak się nie zachowuje. Pracowałem z wersjami od 3.X do 5.X i za każdym razem tak się zachowywał. Moje więc zdanie wynika nie tyle z czytania dokumentacji, co realnego i praktycznego implementowania zapytań. Możliwe, że któraś z (od którejś) wersji zachowuje sie inaczej, ponieważ nie wszystkie na kompie miałem i testowałem. Jak na razie jednak zachowuje się w ten sposób od kilku wersji, więc trudno nie mieć wrażenia, że jednak tutaj determinizm przez ogromnie długi czas występuje, nawet mimo faktu, że nie jest zgodny z dokumentacją. Jakkolwiek dzięki za cynk, przyjrzę się w swoich systemach, czy aby nie zafałszowuje mi to gdzieś wyniku. Póki co bowiem wszystko działa w porządku.
pmir13
Sam fakt, że taka składnia nie przejdzie na sql serverze czy oracle oznacza też że są inne, bardzo wydajne sposoby uzyskiwania potrzebnych danych.
Wracając jednak do tego o co chodzi prawdopodobnie autorowi wątku, najrozsądniejsze wydaje się być najpierw prawidłowe pogrupowanie:
  1. SELECT grupa, max( id ) AS najswiezsza
  2. FROM wiadomosci
  3. WHERE from_id =30 OR to_id =30
  4. GROUP BY grupa

Obie kolumny wchodzące do selecta są prawidłowe, bo grupa jest w GROUP BY, a max jest funkcją agregującą.
To jest bardzo szybkie zapytanie przy prawidłowo ustawionych indeksach. Choćby wiadomości było miliony where na starcie ograniczy to do wiadomości dotyczących pojedynczego użytkownika, a rezultat to bardzo mała tabela, zawierająca tylko tyle rekordów ile jest różnych grup (ile może być grup wiadomości?).
Teraz mając najświeższe wiadomości dla danej grupy możemy połączyć to z powrotem z tą samą tabelą po to by wydobyć dane wiadomości korzystając z faktu, że id jednoznacznie identyfikuje rekord, więc łaczymy po prostu po id<->najswiezsza. Łączymy kilka rekordów z ewentualnie bardzo dużą tablicą, ale po kluczu primary, czyli w najszybszej konfiguracji, więc również będzie bardzo szybko.
Ostatecznie otrzymujemy zapytanie, które będzie sprawnie działać na BARDZO dużej bazie wiadomości:

  1. SELECT wz.wiadomosc FROM
  2. ( SELECT w.grupa, max( id ) AS najswiezsza
  3. FROM wiadomosci w
  4. WHERE w.from_id =30 OR w.to_id =30
  5. GROUP BY w.grupa ) w
  6. JOIN wiadomosci wz
  7. ON wz.id = w.najswiezsza

To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2024 Invision Power Services, Inc.