Итак, вы хотите получить строку с наибольшим значением для 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)
.
Что касается производительности, см. Мой ответ на вопрос « Получение последней записи в каждой группе» . Я пробовал использовать метод подзапроса и метод соединения с использованием дампа данных Stack Overflow. Разница примечательна: в моем тесте метод соединения работал в 278 раз быстрее.
Для достижения наилучших результатов важно, чтобы у вас был правильный индекс!
Что касается вашего метода, использующего переменную @Rank, он не будет работать так, как вы его написали, потому что значения @Rank не будут сброшены до нуля после того, как запрос обработает первую таблицу. Я покажу вам пример.
Я вставил некоторые фиктивные данные с дополнительным полем, которое имеет значение NULL, за исключением строки, которая, как мы знаем, является наибольшей для каждой группы:
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.