Więc chcesz uzyskać wiersz z najwyższym OrderField
na grupę? Zrobiłbym to w ten sposób:
SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
ON t1.GroupId = t2.GroupId AND t1.OrderField < t2.OrderField
WHERE t2.GroupId IS NULL
ORDER BY t1.OrderField; // not needed! (note by Tomas)
(EDIT by Tomasz: Jeśli istnieje więcej rekordów z tym samym OrderField w tej samej grupie i potrzebujesz dokładnie jednego z nich, możesz rozszerzyć warunek:
SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
ON t1.GroupId = t2.GroupId
AND (t1.OrderField < t2.OrderField
OR (t1.OrderField = t2.OrderField AND t1.Id < t2.Id))
WHERE t2.GroupId IS NULL
koniec edycji.)
Innymi słowy, zwróć wiersz t1
dla którego nie ma innego wiersza t2
istnieje z tym samym GroupId
i większe OrderField
. Kiedy t2.*
ma wartość NULL, oznacza to, że lewe sprzężenie zewnętrzne nie znalazło takiego dopasowania, a zatem t1
ma największą wartość OrderField
w grupie.
Bez rang, bez podzapytań. Powinno to działać szybko i zoptymalizować dostęp do t2 za pomocą „Using index”, jeśli masz indeks złożony na (GroupId, OrderField)
.
Jeśli chodzi o wydajność, zobacz moją odpowiedź na Pobieranie ostatniego rekordu w każdej grupie . Próbowałem metody podzapytania i metody join przy użyciu zrzutu danych Stack Overflow. Różnica jest niezwykła:metoda łączenia działała 278 razy szybciej w moim teście.
Aby uzyskać najlepsze wyniki, ważne jest, aby mieć odpowiedni indeks!
Jeśli chodzi o twoją metodę używającą zmiennej @Rank, nie będzie działać tak, jak ją napisałeś, ponieważ wartości @Rank nie zostaną zresetowane do zera po przetworzeniu przez zapytanie pierwszej tabeli. Pokażę ci przykład.
Wstawiłem kilka fikcyjnych danych, z dodatkowym polem o wartości null, z wyjątkiem wiersza, o którym wiemy, że jest największy na grupę:
select * from `Table`;
+---------+------------+------+
| GroupId | OrderField | foo |
+---------+------------+------+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+---------+------------+------+
Możemy pokazać, że ranga wzrasta do trzech dla pierwszej grupy i sześciu dla drugiej grupy, a wewnętrzne zapytanie zwraca je poprawnie:
select GroupId, max(Rank) AS MaxRank
from (
select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField) as t
group by GroupId
+---------+---------+
| GroupId | MaxRank |
+---------+---------+
| 10 | 3 |
| 20 | 6 |
+---------+---------+
Teraz uruchom zapytanie bez warunku złączenia, aby wymusić iloczyn kartezjański wszystkich wierszy, a także pobierzemy wszystkie kolumny:
select s.*, t.*
from (select GroupId, max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as t
group by GroupId) as t
join (
select *, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as s
-- on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;
+---------+---------+---------+------------+------+------+
| GroupId | MaxRank | GroupId | OrderField | foo | Rank |
+---------+---------+---------+------------+------+------+
| 10 | 3 | 10 | 10 | NULL | 7 |
| 20 | 6 | 10 | 10 | NULL | 7 |
| 10 | 3 | 10 | 20 | NULL | 8 |
| 20 | 6 | 10 | 20 | NULL | 8 |
| 20 | 6 | 10 | 30 | foo | 9 |
| 10 | 3 | 10 | 30 | foo | 9 |
| 10 | 3 | 20 | 40 | NULL | 10 |
| 20 | 6 | 20 | 40 | NULL | 10 |
| 10 | 3 | 20 | 50 | NULL | 11 |
| 20 | 6 | 20 | 50 | NULL | 11 |
| 20 | 6 | 20 | 60 | foo | 12 |
| 10 | 3 | 20 | 60 | foo | 12 |
+---------+---------+---------+------------+------+------+
Z powyższego widać, że maksymalna ranga na grupę jest poprawna, ale następnie @Rank nadal rośnie, gdy przetwarza drugą tabelę pochodną, do 7 i wyżej. Tak więc rangi z drugiej tabeli pochodnej nigdy nie będą się pokrywać z rangami z pierwszej tabeli pochodnej.
Musisz dodać kolejną tabelę pochodną, aby zmusić @Rank do zerowania między przetwarzaniem dwóch tabel (i mieć nadzieję, że optymalizator nie zmieni kolejności, w której ocenia tabele, lub użyć STRAIGHT_JOIN, aby temu zapobiec):
select s.*
from (select GroupId, max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as t
group by GroupId) as t
join (select @Rank := 0) r -- RESET @Rank TO ZERO HERE
join (
select *, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as s
on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;
+---------+------------+------+------+
| GroupId | OrderField | foo | Rank |
+---------+------------+------+------+
| 10 | 30 | foo | 3 |
| 20 | 60 | foo | 6 |
+---------+------------+------+------+
Ale optymalizacja tego zapytania jest straszna. Nie może używać żadnych indeksów, tworzy dwie tymczasowe tabele, sortuje je w trudny sposób, a nawet używa bufora łączenia, ponieważ nie może również używać indeksu podczas łączenia tabel tymczasowych. To jest przykładowe wyjście z EXPLAIN
:
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
| 1 | PRIMARY | <derived4> | system | NULL | NULL | NULL | NULL | 1 | Using temporary; Using filesort |
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 2 | |
| 1 | PRIMARY | <derived5> | ALL | NULL | NULL | NULL | NULL | 6 | Using where; Using join buffer |
| 5 | DERIVED | Table | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
| 4 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
| 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 6 | Using temporary; Using filesort |
| 3 | DERIVED | Table | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
Natomiast moje rozwiązanie wykorzystujące lewe sprzężenie zewnętrzne optymalizuje się znacznie lepiej. Nie używa tabeli tymczasowej, a nawet zgłasza "Using index"
co oznacza, że może rozwiązać połączenie, używając tylko indeksu, bez dotykania danych.
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
| 1 | SIMPLE | t2 | ref | GroupId | GroupId | 5 | test.t1.GroupId | 1 | Using where; Using index |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
Prawdopodobnie przeczytasz ludzi, którzy na swoich blogach twierdzą, że „połączenia spowalniają SQL”, ale to nonsens. Słaba optymalizacja spowalnia SQL.