Então você deseja obter a linha com o maior número OrderField
por grupo? Eu faria assim:
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 por Tomas: Se houver mais registros com o mesmo OrderField dentro do mesmo grupo e você precisar exatamente de um deles, você pode querer estender a condição:
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
fim da edição.)
Em outras palavras, retorna a linha t1
para a qual nenhuma outra linhat2
existe com o mesmo GroupId
e um maior OrderField
. Quando t2.*
é NULL, significa que a junção externa esquerda não encontrou tal correspondência e, portanto, t1
tem o maior valor de OrderField
no grupo.
Sem classificações, sem subconsultas. Isso deve ser executado rapidamente e otimizar o acesso a t2 com "Usando índice" se você tiver um índice composto ativado (GroupId, OrderField)
.
Com relação ao desempenho, veja minha resposta em Recuperando o último registro de cada grupo . Tentei um método de subconsulta e o método de junção usando o despejo de dados Stack Overflow. A diferença é notável: o método join foi executado 278 vezes mais rápido em meu teste.
É importante que você tenha o índice correto para obter os melhores resultados!
Com relação ao seu método usando a variável @Rank, ele não funcionará como você o escreveu, porque os valores de @Rank não serão zerados após a consulta ter processado a primeira tabela. Vou te mostrar um exemplo.
Inseri alguns dados fictícios, com um campo extra que é nulo, exceto na linha que sabemos ser a maior por grupo:
select * from `Table`;
+---------+------------+------+
| GroupId | OrderField | foo |
+---------+------------+------+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+---------+------------+------+
Podemos mostrar que a classificação aumenta para três para o primeiro grupo e seis para o segundo grupo, e a consulta interna retorna estes corretamente:
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 |
+---------+---------+
Agora execute a consulta sem condição de junção, para forçar um produto cartesiano de todas as linhas, e também buscaremos todas as colunas:
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 |
+---------+---------+---------+------------+------+------+
Podemos ver acima que a classificação máxima por grupo está correta, mas o @Rank continua a aumentar à medida que processa a segunda tabela derivada, para 7 e acima. Portanto, as classificações da segunda tabela derivada nunca se sobreporão às classificações da primeira tabela derivada.
Você teria que adicionar outra tabela derivada para forçar o @Rank a zerar entre o processamento das duas tabelas (e esperar que o otimizador não altere a ordem em que avalia as tabelas, ou então use STRAIGHT_JOIN para evitar isso):
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 |
+---------+------------+------+------+
Mas a otimização dessa consulta é terrível. Ele não pode usar nenhum índice, cria duas tabelas temporárias, classifica-as da maneira mais difícil e até usa um buffer de junção porque também não pode usar um índice ao juntar tabelas temporárias. Este é um exemplo de resultado de 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 |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
Enquanto minha solução usando a junção externa esquerda otimiza muito melhor. Ele não usa nenhuma tabela temporária e até mesmo relatórios, o "Using index"
que significa que pode resolver a junção usando apenas o índice, sem tocar nos dados.
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
Você provavelmente vai ler pessoas fazendo declarações em seus blogs que "as junções tornam o SQL lento", mas isso é um absurdo. A otimização deficiente torna o SQL lento.