Czy chcesz uzyskać wiersz z najwyższą wartością 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)
( EDYTUJ według Tomasa: Jeśli w tej samej grupie jest więcej rekordów z tym samym polem zamówienia i potrzebujesz dokładnie jednego z nich, możesz chcieć 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
z tym samym GroupId
i większym OrderField
. Kiedy t2.*
ma wartość NULL, oznacza to, że lewe sprzężenie zewnętrzne nie znalazło takiego dopasowania i dlatego t1
ma największą wartość OrderField
w grupie.
Bez rang, bez podzapytań. Powinno to działać szybko i optymalizować dostęp do t2 za pomocą opcji „Korzystanie z indeksu”, jeśli masz włączony indeks złożony (GroupId, OrderField)
.
Jeśli chodzi o wydajność, zobacz moją odpowiedź na temat Pobieranie ostatniego rekordu w każdej grupie . Wypróbowałem metodę podzapytania i metodę łączenia przy użyciu zrzutu danych przepełnienia stosu. Różnica jest niezwykła: w moim teście metoda łączenia działała 278 razy szybciej.
Aby uzyskać najlepsze wyniki, ważne jest, aby mieć odpowiedni indeks!
Jeśli chodzi o metodę używającą zmiennej @Rank, nie będzie ona działać tak, jak ją zapisał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, które ma wartość 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 zapytanie wewnętrzne 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 łączenia, aby wymusić iloczyn kartezjański wszystkich wierszy, a także pobieramy 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
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 pozycja na grupę jest poprawna, ale wtedy @Rank nadal rośnie, gdy przetwarza drugą tabelę pochodną, do 7 i wyżej. Zatem rangi z drugiej tabeli pochodnej w ogóle nie będą się pokrywać z rangami z pierwszej tabeli pochodnej.
Musiałbyś dodać kolejną tabelę pochodną, aby zmusić @Rank do zresetowania do zera między przetwarzaniem dwóch tabel (i mieć nadzieję, że optymalizator nie zmieni kolejności, w której ocenia tabele, lub użyj 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
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. Oto przykładowe dane wyjściowe 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 złącze zewnętrzne optymalizuje się znacznie lepiej. Nie używa tabeli tymczasowej, a nawet raportów, "Using index"
co oznacza, że może rozwiązać łą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 twierdzą na swoich blogach, że „przyłączenia spowalniają SQL”, ale to nonsens. Słaba optymalizacja spowalnia SQL.