Така че искате да получите реда с най-високото OrderField
на група? Бих го направил по следния начин:
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)
(РЕДАКТИРАНЕ от Томаш: Ако има повече записи със същото OrderField в рамките на същата група и имате нужда от точно един от тях, може да искате да разширите условието:
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
край на редакцията.)
С други думи, върнете реда t1
за който няма друг ред t2
съществува със същия GroupId
и по-голямо OrderField
. Когато t2.*
е NULL, това означава, че лявото външно съединение не е намерило такова съвпадение и следователно t1
има най-голямата стойност на OrderField
в групата.
Без рангове, без подзаявки. Това трябва да работи бързо и да оптимизира достъпа до t2 с „Използване на индекс“, ако имате съставен индекс на (GroupId, OrderField)
.
По отношение на производителността вижте моя отговор на Извличане на последния запис във всяка група . Опитах метод на подзаявка и метода на присъединяване, използвайки дъмп на данни за препълване на стека. Разликата е забележителна:методът на присъединяване работи 278 пъти по-бързо в моя тест.
Важно е да имате правилния индекс, за да получите най-добри резултати!
По отношение на вашия метод, използващ променливата @Rank, той няма да работи така, както сте го написали, защото стойностите на @Rank няма да се нулират, след като заявката обработи първата таблица. Ще ви покажа пример.
Вмъкнах някои фиктивни данни с допълнително поле, което е нулево, освен в реда, за който знаем, че е най-голямото за група:
select * from `Table`;
+---------+------------+------+
| GroupId | OrderField | foo |
+---------+------------+------+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+---------+------------+------+
Можем да покажем, че рангът се увеличава до три за първата група и шест за втората група, а вътрешната заявка връща тези правилно:
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 |
+---------+---------+
Сега стартирайте заявката без условие за присъединяване, за да принудите декартово произведение на всички редове и също така извличаме всички колони:
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 |
+---------+---------+---------+------------+------+------+
От горното можем да видим, че максималният ранг на група е правилен, но след това @Rank продължава да се увеличава, докато обработва втората извлечена таблица, до 7 и по-високи. Така че ранговете от втората извлечена таблица изобщо няма да се припокриват с ранговете от първата извлечена таблица.
Ще трябва да добавите друга извлечена таблица, за да принудите @Rank да се нулира между обработката на двете таблици (и да се надяваме, че оптимизаторът няма да промени реда, в който оценява таблиците, или да използва STRAIGHT_JOIN, за да предотврати това):
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 |
+---------+------------+------+------+
Но оптимизацията на тази заявка е ужасна. Той не може да използва никакви индекси, създава две временни таблици, сортира ги по трудния начин и дори използва буфер за присъединяване, защото не може да използва индекс и при присъединяване на временни таблици. Това е примерен изход от 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 |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
Докато моето решение, използващо лявото външно съединение, оптимизира много по-добре. Той не използва временна таблица и дори отчита "Using index"
което означава, че може да разреши присъединяването, използвайки само индекса, без да докосва данните.
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
Вероятно ще прочетете хора, които твърдят в блоговете си, че „съединяванията правят SQL бавен“, но това са глупости. Лошата оптимизация прави SQL бавен.