Ответ 1
Итак, вы хотите получить строку с наивысшим 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)
( ИЗМЕНИТЬ 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, используя дамп данных Stack Overflow. Различие замечательно: метод соединения в моем тесте выполнялся в 278 раз быстрее.
Важно, чтобы у вас был правильный индекс, чтобы получить наилучшие результаты!
Что касается вашего метода с использованием переменной @Rank, он не будет работать так, как вы его написали, потому что значения @Rank не будут reset равны нулю после того, как запрос обработает первую таблицу. Я покажу вам пример.
Я вставил некоторые фиктивные данные, с дополнительным полем, которое является нулевым, кроме строки, которую мы знаем, является наибольшей для каждой группы:
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 с reset равняться нулю между обработкой двух таблиц (и надеемся, что оптимизатор не изменит порядок, в котором он оценивает таблицы, или же используйте 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 |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
В то время как мое решение, использующее левое внешнее соединение, оптимизируется намного лучше. Он не использует таблицу temp и даже отчеты "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 медленным.