То есть вы хотите получить строку с самым высоким 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)
.
Что касается производительности, см. Мой ответ на Получение последней записи в каждомгруппа .Я попробовал метод подзапроса и метод соединения, используя дамп данных переполнения стека.Разница замечательная: метод соединения в моем тесте работал в 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 медленным.