Получить записи с самым высоким / самым маленьким в группе
как это сделать?
прежнее название этого вопроса было"использование ранга (@Rank := @Rank + 1) в сложном запросе с подзапросами - будет ли это работать?" потому что я искал решение, используя ранги, но теперь я вижу, что решение, опубликованное Биллом, намного лучше.
исходный вопрос:
Я пытаюсь составить запрос, который будет принимать последнюю запись из каждой группы с учетом определенного порядка:
SET @Rank=0;
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 := @Rank + 1 AS Rank
from Table
order by OrderField
) as s
on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField
выражение @Rank := @Rank + 1
is обычно используется для ранга, но для меня это выглядит подозрительно при использовании в 2 подзапросах, но инициализируется только один раз. Будет ли это работать таким образом?
и во-вторых, будет ли он работать с одним подзапросом, который оценивается несколько раз? Как подзапрос в предложении where (или having) (другой способ, как написать выше):
SET @Rank=0;
select Table.*, @Rank := @Rank + 1 AS Rank
from Table
having Rank = (select max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from Table as t0
order by OrderField
) as t
where t.GroupId = table.GroupId
)
order by OrderField
спасибо заранее!
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)
(EDIT 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 с помощью "Using index", если у вас есть составной индекс на
(GroupId, OrderField)
.
что касается производительности, см. Мой ответ получение последней записи в группе. Я попробовал метод подзапроса и метод соединения с использованием стека Переполнение дампа данных. Разница примечательна: метод join в моем тесте работал в 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 -- 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 медленным.