Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> grupowanie z jednym polem max
MadMark
post
Post #1





Grupa: Zarejestrowani
Postów: 105
Pomógł: 3
Dołączył: 12.07.2010

Ostrzeżenie: (0%)
-----


cześć,
potrzebuję pomocy z grupowaniem tj. mam tabelę
  1. CREATE TABLE modifications (
  2. id int PRIMARY KEY AUTO_INCREMENT,
  3. date timestamp DEFAULT timestmp NOT NULL,
  4. object_id int, -- fk to objects
  5. status_id int, -- fk to statuses
  6. );


i chciałbym pobrać wszystkie wiersze, w których status_id jest najnowszy (max(date)) w ramach jednego object_id. Mówiąc krótko, dla każdego okiektu potrzebny mi najnowszy status.

Działa (ale za wolno, bo ok. 200 ms)
  1. SELECT f.* FROM modifications f WHERE (f.obiect_id, f.date) IN (SELECT g.obiect_id AS obiect_id, max(g.date) AS date FROM modifications g GROUP BY g.obiect_id)


Samo zrobienie:
  1. SELECT * FROM modifications GROUP BY obiect_id HAVING date=max(date)

nie działa, ponieważ zwraca pierwszy napotkany status, a nie ten pasujący do maksymalnej daty (nie najnowszy, a pierwszy insertowany).

Jak powinno wyglądać najprostsze zapytanie, żeby zwrócić najnowszy status dla każdego obiektu ?
Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi (1 - 10)
trueblue
post
Post #2





Grupa: Zarejestrowani
Postów: 6 806
Pomógł: 1828
Dołączył: 11.03.2014

Ostrzeżenie: (0%)
-----


  1. SELECT object_id,MAX(status_id) FROM modifications GROUP BY object_id;
Go to the top of the page
+Quote Post
MadMark
post
Post #3





Grupa: Zarejestrowani
Postów: 105
Pomógł: 3
Dołączył: 12.07.2010

Ostrzeżenie: (0%)
-----


Cytat(trueblue @ 12.12.2016, 11:35:50 ) *
  1. SELECT object_id,MAX(status_id) FROM modifications GROUP BY object_id;

Problem polega na tym, żę taka wersja nie wchodzi w grę. status_id może być w różnej kolejności np. Usunięty = 1, Nowy = 2 - czyli, jeśli status będzie maxem, to nie będzie usuniętych. Dlatego potrzebuję użyć daty (IMG:style_emoticons/default/smile.gif)
Go to the top of the page
+Quote Post
mmmmmmm
post
Post #4





Grupa: Zarejestrowani
Postów: 1 421
Pomógł: 310
Dołączył: 18.04.2012

Ostrzeżenie: (0%)
-----


Twoje pierwsze zapytanie jest jedynym (oprócz takiego samego JOINa), które jest prawidłowe i szybkie. 200 ms to nie tragedia.
Drugie (to z HAVING) to tylko na MySQL się wykona. Każda inna baza zasygnalizuje błąd.
Jeśli jednak to rozwiązanie cię nie satysfakcjonuje to pomyśl nad zmianą architektury - tak, bys przechwowywał (w tej lub innej tabeli) tylko najnowsze statusy
Go to the top of the page
+Quote Post
trueblue
post
Post #5





Grupa: Zarejestrowani
Postów: 6 806
Pomógł: 1828
Dołączył: 11.03.2014

Ostrzeżenie: (0%)
-----


Źle przecyzytałem problem.
Czy większa data oznacza większe id, czy też dla mniejszego id może być większa data (oczywiście w ramach tego samego object_id)?
Go to the top of the page
+Quote Post
MadMark
post
Post #6





Grupa: Zarejestrowani
Postów: 105
Pomógł: 3
Dołączył: 12.07.2010

Ostrzeżenie: (0%)
-----


Cytat(trueblue @ 12.12.2016, 12:01:18 ) *
Źle przecyzytałem problem.
Czy większa data oznacza większe id, czy też dla mniejszego id może być większa data (oczywiście w ramach tego samego object_id)?

Większa data może oznaczać mniejsze ID. Dlatego właśnie potrzebuje wybrać to id, które należy do rekordu z największą datą, mysql zwraca mi niestety pierwsze napotkane.
Go to the top of the page
+Quote Post
trueblue
post
Post #7





Grupa: Zarejestrowani
Postów: 6 806
Pomógł: 1828
Dołączył: 11.03.2014

Ostrzeżenie: (0%)
-----


A jak to działa w ten sposób?
  1. SELECT object_id,status_id FROM modifications AS m
  2. JOIN (SELECT object_id,MAX(date) AS max_date FROM modifications GROUP BY object_id) AS m2 ON m2.max_date=m.date AND m2.object_id=m.object_id;

Na dacie przydałby się indeks.
Go to the top of the page
+Quote Post
MadMark
post
Post #8





Grupa: Zarejestrowani
Postów: 105
Pomógł: 3
Dołączył: 12.07.2010

Ostrzeżenie: (0%)
-----


Cytat(mmmmmmm @ 12.12.2016, 11:56:16 ) *
Twoje pierwsze zapytanie jest jedynym (oprócz takiego samego JOINa), które jest prawidłowe i szybkie. 200 ms to nie tragedia.
Drugie (to z HAVING) to tylko na MySQL się wykona. Każda inna baza zasygnalizuje błąd.
Jeśli jednak to rozwiązanie cię nie satysfakcjonuje to pomyśl nad zmianą architektury - tak, bys przechwowywał (w tej lub innej tabeli) tylko najnowsze statusy

200 to tragedia, bo ta sama konstrukcja z self-joinem w postaci:

  1. SELECT g.* FROM (SELECT obiect_id, max(date) AS date FROM modifications GROUP BY obiect_id) f
  2. LEFT JOIN modifications g ON (f.obiect_id=g.obiect_id AND f.date=g.date)

wykonuje sie 4x szybciej (czasem nawet 5), ale tworzy tabelę tymczasową w myisamie i większość czasu zabiera przerzucanie danych między innodb i myisamem.
To jest serwer współdzielony, więc nie ma szans na zmianę tabel tymczasowych z myisam na innodb, żeby nie tracić na to czasu, bo wtedy to (i pozostałe) zapytanie wykonywałoby się w okolicach 10 ms (tak wynika z profilera).

Dlatego najlepiej by było zmieścić to w jednym zapytaniu, bez widoków, bez sub-queries, bez unionów.

Ten post edytował MadMark 12.12.2016, 12:43:58
Go to the top of the page
+Quote Post
mmmmmmm
post
Post #9





Grupa: Zarejestrowani
Postów: 1 421
Pomógł: 310
Dołączył: 18.04.2012

Ostrzeżenie: (0%)
-----


Sorry, ale ja nie widze różnic pomiędzy tymi zapytaniami. Tym z (x,y) IN (..,..) a JOIN. Zresztą już wcześniej napisałem o JOIN. Optymalizator kosztowy powinien je na taki sam algorytm przerobić.
Go to the top of the page
+Quote Post
MadMark
post
Post #10





Grupa: Zarejestrowani
Postów: 105
Pomógł: 3
Dołączył: 12.07.2010

Ostrzeżenie: (0%)
-----


Cytat(mmmmmmm @ 12.12.2016, 13:55:15 ) *
Sorry, ale ja nie widze różnic pomiędzy tymi zapytaniami. Tym z (x,y) IN (..,..) a JOIN. Zresztą już wcześniej napisałem o JOIN. Optymalizator kosztowy powinien je na taki sam algorytm przerobić.

Niestety, nie przerabia. mysql 5.5, ok 10k rekordów - jeśli to coś zmienia. Tak jak pisałem wcześniej zapytanie wykonuje się zbyt długo w pierwszej wersji, w drugiej krócej, ale też wiem, że można lepiej - tylko technicznie z wiedzą nie domagam.
Go to the top of the page
+Quote Post
mmmmmmm
post
Post #11





Grupa: Zarejestrowani
Postów: 1 421
Pomógł: 310
Dołączył: 18.04.2012

Ostrzeżenie: (0%)
-----


Załóż indeks na parę (obiect_id,date) i wklej tu trzy explainy:
1.
SELECT g.* FROM (SELECT obiect_id, max(date) AS date FROM modifications GROUP BY obiect_id) f
LEFT JOIN modifications g ON (f.obiect_id=g.obiect_id AND f.date=g.date)
2.
SELECT f.* FROM modifications f WHERE (f.obiect_id, f.date) IN (SELECT g.obiect_id AS obiect_id, max(g.date) AS date FROM modifications g GROUP BY g.obiect_id)
3.
SELECT g.* FROM (SELECT obiect_id, max(date) AS date FROM modifications GROUP BY obiect_id) f
LEFT JOIN modifications g using (obiect_id,date)

3 to jest to samo, co 1, ale dla pewności.
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 22.08.2025 - 20:36