Отже, ви хочете отримати рядок із найвищим показником для 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)
( EDIT від 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)
.
Щодо продуктивності, див. Мою відповідь на Отримання останнього запису в кожній групі . Я спробував метод підзапиту та метод join, використовуючи дамп даних переповнення стека. Різниця надзвичайна: у моєму тесті метод приєднання працював у 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
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
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 повільним.