Как это сделать?
Прежнее название этого вопроса было « использование ранга (@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
обычно используется для ранжирования, но для меня это выглядит подозрительно, когда используется в двух подзапросах, но инициализируется только один раз. Так будет ли работать?
И во-вторых, будет ли он работать с одним подзапросом, который выполняется несколько раз? Как подзапрос в предложении where (или имеющий) (другой способ написания вышеупомянутого):
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
Заранее спасибо!
Ответы:
Итак, вы хотите получить строку с наибольшим значением для
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 -- 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.
источник
@Rank1
и@Rank2
, по одному для каждого подзапроса? Это решило бы проблему? Будет ли это быстрее вашего решения?@Rank1
и не@Rank2
имело бы никакого значения.... AND t1.foo = t2.foo
чтобы позже получить правильные результаты дляWHERE ... AND foo='bar'