Como fazer isso?
O título anterior desta questão era " usando classificação (@Rank: = @Rank + 1) em consulta complexa com subconsultas - funcionará? " Porque eu estava procurando uma solução usando classificações, mas agora vejo que a solução postada por Bill é muito, muito melhor.
Questão original:
Estou tentando compor uma consulta que levaria o último registro de cada grupo dada alguma ordem definida:
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
Expression @Rank := @Rank + 1
é normalmente usado para classificação, mas para mim parece suspeito quando usado em 2 subconsultas, mas inicializado apenas uma vez. Vai funcionar assim?
Em segundo lugar, funcionará com uma subconsulta avaliada várias vezes? Como subconsulta na cláusula where (ou having) (outra forma de escrever acima):
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
Desde já, obrigado!
Respostas:
Então você deseja obter a linha com o maior número
OrderField
por grupo? Eu faria assim:( 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:
fim da edição.)
Em outras palavras, retorna a linha
t1
para a qual nenhuma outra linhat2
existe com o mesmoGroupId
e um maiorOrderField
. Quandot2.*
é NULL, significa que a junção externa esquerda não encontrou tal correspondência e, portanto,t1
tem o maior valor deOrderField
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:
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:
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:
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):
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
: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.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.
fonte
@Rank1
e@Rank2
, um para cada subconsulta? Isso resolveria o problema? Isso seria mais rápido do que sua solução?@Rank1
e@Rank2
não faria diferença.... AND t1.foo = t2.foo
para mais tarde obter os resultados corretos paraWHERE ... AND foo='bar'