Há uma tabela messages
que contém dados como mostrado abaixo:
Id Name Other_Columns
-------------------------
1 A A_data_1
2 A A_data_2
3 A A_data_3
4 B B_data_1
5 B B_data_2
6 C C_data_1
Se eu executar uma consulta select * from messages group by name
, obterá o resultado como:
1 A A_data_1
4 B B_data_1
6 C C_data_1
Qual consulta retornará o seguinte resultado?
3 A A_data_3
5 B B_data_2
6 C C_data_1
Ou seja, o último registro em cada grupo deve ser retornado.
No momento, esta é a consulta que eu uso:
SELECT
*
FROM (SELECT
*
FROM messages
ORDER BY id DESC) AS x
GROUP BY name
Mas isso parece altamente ineficiente. Alguma outra maneira de obter o mesmo resultado?
sql
mysql
group-by
greatest-n-per-group
Vijay Dev
fonte
fonte
Respostas:
O MySQL 8.0 agora suporta funções de janelas, como quase todas as implementações populares de SQL. Com esta sintaxe padrão, podemos escrever as melhores consultas de n por grupo:
Abaixo está a resposta original que escrevi para esta pergunta em 2009:
Eu escrevo a solução desta maneira:
Em relação ao desempenho, uma solução ou outra pode ser melhor, dependendo da natureza dos seus dados. Portanto, você deve testar as duas consultas e usar a que tem melhor desempenho, considerando seu banco de dados.
Por exemplo, eu tenho uma cópia do despejo de dados StackOverflow August . Vou usar isso para comparações. Existem 1.114.357 linhas na
Posts
tabela. Isso está sendo executado no MySQL 5.0.75 no meu Macbook Pro 2.40GHz.Escreverei uma consulta para encontrar a postagem mais recente para um determinado ID de usuário (meu).
Primeiro, usando a técnica mostrada pelo @Eric com o
GROUP BY
em uma subconsulta:Até a
EXPLAIN
análise leva mais de 16 segundos:Agora produza o mesmo resultado de consulta usando minha técnica com
LEFT JOIN
:A
EXPLAIN
análise mostra que ambas as tabelas podem usar seus índices:Aqui está o DDL da minha
Posts
tabela:fonte
<=
não ajudará se você tiver uma coluna não exclusiva. Você deve usar uma coluna exclusiva como um desempatador.UPD: 31-03-2017, a versão 5.7.5 do MySQL tornou a opção ONLY_FULL_GROUP_BY ativada por padrão (portanto, as consultas não determinísticas de GROUP BY foram desativadas). Além disso, eles atualizaram a implementação do GROUP BY e a solução pode não funcionar mais como o esperado, mesmo com a opção desativada. É preciso verificar.
A solução de Bill Karwin acima funciona bem quando a contagem de itens dentro dos grupos é pequena, mas o desempenho da consulta fica ruim quando os grupos são grandes, já que a solução exige
n*n/2 + n/2
apenasIS NULL
comparações.Fiz meus testes em uma tabela de
18684446
linhas do InnoDB com1182
grupos. A tabela contém resultados de teste para testes funcionais e possui a(test_id, request_id)
chave primária. Assim,test_id
é um grupo e eu estava procurando o últimorequest_id
para cada umtest_id
.A solução de Bill já está em execução há várias horas no meu dell e4310 e não sei quando ele será finalizado, embora opere em um índice de cobertura (daqui
using index
em EXPLAIN).Eu tenho algumas outras soluções baseadas nas mesmas idéias:
(group_id, item_value)
par será o último valor em cada umgroup_id
, que será o primeiro para cada umgroup_id
se percorrermos o índice em ordem decrescente;3 maneiras pelas quais o MySQL usa índices é um ótimo artigo para entender alguns detalhes.
Solução 1
Este é incrivelmente rápido, leva cerca de 0,8 segundos nas minhas 18 milhões de linhas:
Se você deseja alterar a ordem para ASC, coloque-a em uma subconsulta, retorne apenas os IDs e use-os como subconsulta para ingressar no restante das colunas:
Este leva cerca de 1,2 segundos nos meus dados.
Solução 2
Aqui está outra solução que leva cerca de 19 segundos para minha tabela:
Ele retorna testes em ordem decrescente também. É muito mais lento, pois faz uma varredura completa do índice, mas está aqui para lhe dar uma idéia de como gerar N max linhas para cada grupo.
A desvantagem da consulta é que seu resultado não pode ser armazenado em cache pelo cache da consulta.
fonte
SELECT test_id, request_id FROM testresults GROUP BY test_id;
retornaria o mínimo request_id para cada test_id.Use sua subconsulta para retornar o agrupamento correto, porque você está no meio do caminho.
Tente o seguinte:
Se não for,
id
você deseja o máximo de:Dessa forma, você evita subconsultas correlatas e / ou pedidos em suas subconsultas, que tendem a ser muito lentas / ineficientes.
fonte
other_col
: se essa coluna não for exclusiva, você poderá obter vários registros com a mesmaname
, se eles estiverem associadosmax(other_col)
. Encontrei este post que descreve uma solução para minhas necessidades, onde preciso exatamente de um registro porname
.INDEX(name, id)
eINDEX(name, other_col)
Cheguei a uma solução diferente, que é obter os IDs para a última postagem em cada grupo e selecionar na tabela de mensagens usando o resultado da primeira consulta como argumento para uma
WHERE x IN
construção:Não sei como isso funciona em comparação com algumas das outras soluções, mas funcionou espetacularmente para minha tabela com mais de 3 milhões de linhas. (4 segundos de execução com mais de 1200 resultados)
Isso deve funcionar no MySQL e no SQL Server.
fonte
Solução por sub-consulta violino Link
Solução Por condição de junção link violino
A razão para este post é fornecer apenas o link do violino. O mesmo SQL já é fornecido em outras respostas.
fonte
Uma abordagem com velocidade considerável é a seguinte.
Resultado
fonte
id
está ordenado da maneira que você precisa. No caso geral, é necessária alguma outra coluna.Aqui estão duas sugestões. Primeiro, se o mysql suporta ROW_NUMBER (), é muito simples:
Estou assumindo que "último" significa o último na ordem de identificação. Caso contrário, altere a cláusula ORDER BY da janela ROW_NUMBER () de acordo. Se ROW_NUMBER () não estiver disponível, esta é outra solução:
Segundo, se isso não acontecer, geralmente é uma boa maneira de prosseguir:
Em outras palavras, selecione as mensagens nas quais não há mensagens de identificação posterior com o mesmo nome.
fonte
ROW_NUMBER()
e CTEs.Ainda não testei com banco de dados grande, mas acho que isso poderia ser mais rápido do que juntar tabelas:
fonte
Aqui está outra maneira de obter o último registro relacionado usando
GROUP_CONCAT
com a ordem de eSUBSTRING_INDEX
escolher um dos registros da listaA consulta acima agrupará todos os
Other_Columns
que estão no mesmoName
grupo e usarORDER BY id DESC
juntará todos osOther_Columns
em um grupo específico em ordem decrescente com o separador fornecido no meu caso que eu usei||
, usandoSUBSTRING_INDEX
sobre esta lista escolheremos o primeiroFiddle Demo
fonte
group_concat_max_len
limita quantas linhas você pode manipular.Claramente, existem muitas maneiras diferentes de obter os mesmos resultados, sua pergunta parece ser o que é uma maneira eficiente de obter os últimos resultados em cada grupo no MySQL. Se você estiver trabalhando com grandes quantidades de dados e assumindo que está usando o InnoDB até mesmo com as versões mais recentes do MySQL (como 5.7.21 e 8.0.4-rc), pode não haver uma maneira eficiente de fazer isso.
Às vezes, precisamos fazer isso com tabelas com mais de 60 milhões de linhas.
Para esses exemplos, usarei dados com apenas cerca de 1,5 milhão de linhas em que as consultas precisariam encontrar resultados para todos os grupos nos dados. Em nossos casos reais, muitas vezes precisaríamos retornar dados de cerca de 2.000 grupos (o que, hipoteticamente, não seria necessário examinar muito dos dados).
Vou usar as seguintes tabelas:
A tabela de temperatura é preenchida com cerca de 1,5 milhão de registros aleatórios e com 100 grupos diferentes. O grupo selected_ é preenchido com esses 100 grupos (em nossos casos, normalmente seria inferior a 20% para todos os grupos).
Como esses dados são aleatórios, significa que várias linhas podem ter os mesmos registros de data e hora registrados. O que queremos é obter uma lista de todos os grupos selecionados na ordem do groupID com o último timestamp registrado para cada grupo e, se o mesmo grupo tiver mais de uma linha correspondente assim, o último ID correspondente dessas linhas.
Se, hipoteticamente, o MySQL tivesse uma função last () que retornasse valores da última linha em uma cláusula ORDER BY especial, poderíamos simplesmente fazer:
que precisaria examinar apenas algumas 100 linhas nesse caso, pois não usa nenhuma das funções normais de GROUP BY. Isso seria executado em 0 segundos e, portanto, seria altamente eficiente. Note que normalmente no MySQL veríamos uma cláusula ORDER BY seguindo a cláusula GROUP BY, no entanto, esta cláusula ORDER BY é usada para determinar a ORDER da última função (), se fosse depois do GROUP BY, ela estaria ordenando os GROUPS. Se nenhuma cláusula GROUP BY estiver presente, os últimos valores serão os mesmos em todas as linhas retornadas.
No entanto, o MySQL não possui isso, então vamos examinar diferentes idéias do que ele possui e provar que nenhuma delas é eficiente.
Exemplo 1
Isso examinou 3.009.254 linhas e levou ~ 0,859 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 2
Isso examinou 1.505.331 linhas e levou ~ 1,25 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 3
Isso examinou 3.009.685 linhas e levou ~ 1,95 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 4
Isso examinou 6.137.810 linhas e levou ~ 2,2 segundos em 5.7.21 e um pouco mais em 8.0.4-rc
Exemplo 5
Isso examinou 6.017.808 linhas e levou ~ 4,2 segundos no 8.0.4-rc
Exemplo 6
Este examinou 6.017.908 linhas e levou ~ 17,5 segundos no 8.0.4-rc
Exemplo 7
Este estava levando uma eternidade, então eu tive que matá-lo.
fonte
SELECT DISTINCT(groupID)
é rápido e fornecerá todos os dados necessários para criar essa consulta. Você deve ficar bem com o tamanho da consulta, desde que não excedamax_allowed_packet
, o padrão é 4 MB no MySQL 5.7.veremos como você pode usar o MySQL para obter o último registro em um grupo de registros. Por exemplo, se você tiver este conjunto de resultados de postagens.
id category_id post_title
1 1 Title 1
2 1 Title 2
3 1 Title 3
4 2 Title 4
5 2 Title 5
6 3 Title 6
Quero poder obter a última postagem em cada categoria, que são Título 3, Título 5 e Título 6. Para obter as postagens por categoria, você utilizará o teclado MySQL Group By.
select * from posts group by category_id
Mas os resultados que obtemos dessa consulta são.
id category_id post_title
1 1 Title 1
4 2 Title 4
6 3 Title 6
O grupo por sempre retornará o primeiro registro no grupo no conjunto de resultados.
SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );
Isso retornará as postagens com os IDs mais altos em cada grupo.
id category_id post_title
3 1 Title 3
5 2 Title 5
6 3 Title 6
Referência Clique Aqui
fonte
fonte
Aqui está a minha solução:
fonte
SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
.Tente o seguinte:
fonte
Olá, @Vijay Dev, se as mensagens da sua tabela contiverem ID, que é a chave primária de incremento automático, para buscar a base de registro mais recente na chave primária que sua consulta deve ler da seguinte forma:
fonte
Você pode ver aqui também.
http://sqlfiddle.com/#!9/ef42b/9
PRIMEIRA SOLUÇÃO
SEGUNDA SOLUÇÃO
fonte
fonte
**
Olá, esta consulta pode ajudar:
**
fonte
Existe alguma maneira de usar esse método para excluir duplicatas em uma tabela? O conjunto de resultados é basicamente uma coleção de registros exclusivos; portanto, se pudéssemos excluir todos os registros que não estão no conjunto de resultados, efetivamente não teríamos duplicatas? Eu tentei isso, mas o mySQL deu um erro de 1093.
Existe uma maneira de talvez salvar a saída em uma variável temp e excluir de NOT IN (variável temp)? @ Bill obrigado por uma solução muito útil.
EDIT: Acho que encontrei a solução:
fonte
A consulta abaixo funcionará bem conforme sua pergunta.
fonte
Se você deseja a última linha para cada um
Name
, é possível atribuir um número de linha a cada grupo de linhas porName
e ordemId
em ordem decrescente.INQUERIR
SQL Fiddle
fonte
Que tal agora:
Eu tive um problema semelhante (no postgresql resistente) e em uma tabela de registros de 1 milhão. Esta solução leva 1,7s vs 44s produzidos por aquele com LEFT JOIN. No meu caso, tive que filtrar o campo correspondente do seu nome contra valores NULL, resultando em desempenhos ainda melhores em 0,2 segundos
fonte
Se o desempenho é realmente sua preocupação, você pode introduzir uma nova coluna na tabela chamada
IsLastInGroup
do tipo BIT.Defina-o como true nas colunas que são as últimas e mantenha-o a cada linha inserida / atualizada / excluída. As gravações serão mais lentas, mas você se beneficiará das leituras. Depende do seu caso de uso e eu o recomendo apenas se você estiver focado na leitura.
Portanto, sua consulta será semelhante a:
fonte
fonte
Você pode agrupar contando e também obter o último item do grupo, como:
fonte
A esperança abaixo da consulta Oracle pode ajudar:
fonte
Outra abordagem:
Encontre a propriedade com o max m2_price dentro de cada programa (n propriedades em 1 programa):
fonte