Recuperando o último registro em cada grupo - MySQL


958

Há uma tabela messagesque 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?


2
ver resposta aceite em stackoverflow.com/questions/1379565/... para uma solução mais eficiente
eyaler


7
Por que você não pode simplesmente adicionar DESC, ou seja, select * from grupo mensagens pelo nome DESC
Kim príncipe


2
@KimPrince Parece que a resposta que você está sugerindo não faz o que é esperado! Eu apenas tentei o seu método e levou PRIMEIRA linha para cada grupo e solicitei DESC. NÃO leva a última linha de cada grupo
Ayrat 22/17

Respostas:


970

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:

WITH ranked_messages AS (
  SELECT m.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id DESC) AS rn
  FROM messages AS m
)
SELECT * FROM ranked_messages WHERE rn = 1;

Abaixo está a resposta original que escrevi para esta pergunta em 2009:


Eu escrevo a solução desta maneira:

SELECT m1.*
FROM messages m1 LEFT JOIN messages m2
 ON (m1.name = m2.name AND m1.id < m2.id)
WHERE m2.id IS NULL;

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 Poststabela. 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 BYem uma subconsulta:

SELECT p1.postid
FROM Posts p1
INNER JOIN (SELECT pi.owneruserid, MAX(pi.postid) AS maxpostid
            FROM Posts pi GROUP BY pi.owneruserid) p2
  ON (p1.postid = p2.maxpostid)
WHERE p1.owneruserid = 20860;

1 row in set (1 min 17.89 sec)

Até a EXPLAINanálise leva mais de 16 segundos:

+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
| id | select_type | table      | type   | possible_keys              | key         | key_len | ref          | rows    | Extra       |
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                       | NULL        | NULL    | NULL         |   76756 |             | 
|  1 | PRIMARY     | p1         | eq_ref | PRIMARY,PostId,OwnerUserId | PRIMARY     | 8       | p2.maxpostid |       1 | Using where | 
|  2 | DERIVED     | pi         | index  | NULL                       | OwnerUserId | 8       | NULL         | 1151268 | Using index | 
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
3 rows in set (16.09 sec)

Agora produza o mesmo resultado de consulta usando minha técnica com LEFT JOIN:

SELECT p1.postid
FROM Posts p1 LEFT JOIN posts p2
  ON (p1.owneruserid = p2.owneruserid AND p1.postid < p2.postid)
WHERE p2.postid IS NULL AND p1.owneruserid = 20860;

1 row in set (0.28 sec)

A EXPLAINanálise mostra que ambas as tabelas podem usar seus índices:

+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
| id | select_type | table | type | possible_keys              | key         | key_len | ref   | rows | Extra                                |
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
|  1 | SIMPLE      | p1    | ref  | OwnerUserId                | OwnerUserId | 8       | const | 1384 | Using index                          | 
|  1 | SIMPLE      | p2    | ref  | PRIMARY,PostId,OwnerUserId | OwnerUserId | 8       | const | 1384 | Using where; Using index; Not exists | 
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
2 rows in set (0.00 sec)

Aqui está o DDL da minha Poststabela:

CREATE TABLE `posts` (
  `PostId` bigint(20) unsigned NOT NULL auto_increment,
  `PostTypeId` bigint(20) unsigned NOT NULL,
  `AcceptedAnswerId` bigint(20) unsigned default NULL,
  `ParentId` bigint(20) unsigned default NULL,
  `CreationDate` datetime NOT NULL,
  `Score` int(11) NOT NULL default '0',
  `ViewCount` int(11) NOT NULL default '0',
  `Body` text NOT NULL,
  `OwnerUserId` bigint(20) unsigned NOT NULL,
  `OwnerDisplayName` varchar(40) default NULL,
  `LastEditorUserId` bigint(20) unsigned default NULL,
  `LastEditDate` datetime default NULL,
  `LastActivityDate` datetime default NULL,
  `Title` varchar(250) NOT NULL default '',
  `Tags` varchar(150) NOT NULL default '',
  `AnswerCount` int(11) NOT NULL default '0',
  `CommentCount` int(11) NOT NULL default '0',
  `FavoriteCount` int(11) NOT NULL default '0',
  `ClosedDate` datetime default NULL,
  PRIMARY KEY  (`PostId`),
  UNIQUE KEY `PostId` (`PostId`),
  KEY `PostTypeId` (`PostTypeId`),
  KEY `AcceptedAnswerId` (`AcceptedAnswerId`),
  KEY `OwnerUserId` (`OwnerUserId`),
  KEY `LastEditorUserId` (`LastEditorUserId`),
  KEY `ParentId` (`ParentId`),
  CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`PostTypeId`) REFERENCES `posttypes` (`PostTypeId`)
) ENGINE=InnoDB;

8
Mesmo? O que acontece se você tiver uma tonelada de entradas? Por exemplo, se você estiver trabalhando com um controle de versão interno, digamos, e tiver uma tonelada de versões por arquivo, esse resultado da junção seria enorme. Você já comparou o método de subconsulta com este? Estou muito curioso para saber o que venceria, mas não o suficiente para não perguntar primeiro.
Eric

2
Fiz alguns testes. Em uma tabela pequena (~ 300k registros, ~ 190k grupos, grupos não muito grandes ou algo assim), as consultas são vinculadas (8 segundos cada).
Eric

1
@ BillKarwin: Veja meta.stackexchange.com/questions/123017 , especialmente os comentários abaixo da resposta de Adam Rackis. Deixe-me saber se você deseja recuperar sua resposta sobre a nova pergunta.
Robert Harvey

3
@ Tim, não, <=não ajudará se você tiver uma coluna não exclusiva. Você deve usar uma coluna exclusiva como um desempatador.
Bill Karwin

2
O desempenho diminui exponencialmente à medida que o número de linhas aumenta ou quando os grupos se tornam maiores. Por exemplo, um grupo composto por 5 datas produzirá 4 + 3 + 2 + 1 + 1 = 11 linhas via junção esquerda, das quais uma linha é filtrada no final. O desempenho da união com resultados agrupados é quase linear. Seus testes parecem falhos.
Salman A

148

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/2apenas IS NULLcomparações.

Fiz meus testes em uma tabela de 18684446linhas do InnoDB com 1182grupos. 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 último request_idpara cada um test_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 indexem EXPLAIN).

Eu tenho algumas outras soluções baseadas nas mesmas idéias:

  • se o índice subjacente for o índice BTREE (que geralmente é o caso), o maior (group_id, item_value)par será o último valor em cada um group_id, que será o primeiro para cada um group_idse percorrermos o índice em ordem decrescente;
  • se lemos os valores cobertos por um índice, os valores são lidos na ordem do índice;
  • cada índice contém implicitamente colunas de chave primária anexadas a ele (ou seja, a chave primária está no índice de cobertura). Nas soluções abaixo, eu opero diretamente na chave primária, no seu caso, você só precisará adicionar colunas da chave primária no resultado.
  • em muitos casos, é muito mais barato coletar os IDs de linha necessários na ordem necessária em uma subconsulta e associar o resultado da subconsulta ao ID. Como para cada linha no resultado da subconsulta, o MySQL precisará de uma única busca com base na chave primária, a subconsulta será colocada primeiro na junção e as linhas serão exibidas na ordem dos IDs na subconsulta (se omitirmos ORDER BY explícito para a junção)

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:

SELECT test_id, MAX(request_id) AS request_id
FROM testresults
GROUP BY test_id DESC;

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:

SELECT test_id, request_id
FROM (
    SELECT test_id, MAX(request_id) AS request_id
    FROM testresults
    GROUP BY test_id DESC) as ids
ORDER BY test_id;

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:

SELECT test_id, request_id
FROM testresults, (SELECT @group:=NULL) as init
WHERE IF(IFNULL(@group, -1)=@group:=test_id, 0, 1)
ORDER BY test_id DESC, request_id DESC

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.


Conecte-se a um despejo de suas tabelas para que as pessoas possam testá-lo em suas plataformas.
Pacerier 03/02

3
Solução 1 pode não trabalho, não pode seleccionar REQUEST_ID sem ter que no grupo pela cláusula,
giò

2
@ giò, esta é a resposta é de 5 anos de idade. Até o MySQL 5.7.5, o ONLY_FULL_GROUP_BY foi desativado por padrão e esta solução funcionou imediatamente , dev.mysql.com/doc/relnotes/mysql/5.7/en/… . Agora não tenho certeza se a solução ainda funciona quando você desativa o modo, porque a implementação do GROUP BY foi alterada.
newtover 31/03

Se você quisesse o ASC na primeira solução, funcionaria se você girasse MAX para MIN?
Jin

@JinIzzraeel, você tem MIN por padrão na parte superior de cada grupo (é a ordem do índice de cobertura): SELECT test_id, request_id FROM testresults GROUP BY test_id;retornaria o mínimo request_id para cada test_id.
newtover

102

Use sua subconsulta para retornar o agrupamento correto, porque você está no meio do caminho.

Tente o seguinte:

select
    a.*
from
    messages a
    inner join 
        (select name, max(id) as maxid from messages group by name) as b on
        a.id = b.maxid

Se não for, idvocê deseja o máximo de:

select
    a.*
from
    messages a
    inner join 
        (select name, max(other_col) as other_col 
         from messages group by name) as b on
        a.name = b.name
        and a.other_col = b.other_col

Dessa forma, você evita subconsultas correlatas e / ou pedidos em suas subconsultas, que tendem a ser muito lentas / ineficientes.


1
Observe uma ressalva para a solução com other_col: se essa coluna não for exclusiva, você poderá obter vários registros com a mesma name, se eles estiverem associados max(other_col). Encontrei este post que descreve uma solução para minhas necessidades, onde preciso exatamente de um registro por name.
precisa

Em algumas situações, você só pode usar esta solução, mas a solução aceita.
tom10271

Na minha experiência, é agrupar toda a maldita tabela de mensagens que tende a ser lenta / ineficiente! Em outras palavras, observe que a subconsulta requer uma verificação completa da tabela e faz um agrupamento para inicializar ... a menos que seu otimizador esteja fazendo algo que o meu não esteja. Portanto, essa solução depende muito de manter a tabela inteira na memória.
Timo

Aqueles se beneficiariam INDEX(name, id)eINDEX(name, other_col)
Rick James

55

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 INconstrução:

SELECT id, name, other_columns
FROM messages
WHERE id IN (
    SELECT MAX(id)
    FROM messages
    GROUP BY name
);

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.


Apenas verifique se você possui um índice (nome, ID).
Samuel Åslund

1
Muito melhor do que a auto junta
anwerj

Eu aprendi algo de você que é um trabalho bom e esta consulta é mais rápido
Humphrey

33

Solução por sub-consulta violino Link

select * from messages where id in
(select max(id) from messages group by Name)

Solução Por condição de junção link violino

select m1.* from messages m1 
left outer join messages m2 
on ( m1.id<m2.id and m1.name=m2.name )
where m2.id is null

A razão para este post é fornecer apenas o link do violino. O mesmo SQL já é fornecido em outras respostas.


1
O @AlexanderSuraphel mysql5.5 não está disponível no fiddle agora, o link do fiddle foi criado usando isso. Agora, um dia em que o violino suporta o mysql5.6, mudei o banco de dados para o mysql 5.6 e sou capaz de criar esquemas e executar o sql.
Vipin

8

Uma abordagem com velocidade considerável é a seguinte.

SELECT * 
FROM messages a
WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)

Resultado

Id  Name    Other_Columns
3   A   A_data_3
5   B   B_data_2
6   C   C_data_1

Isso pressupõe que idestá ordenado da maneira que você precisa. No caso geral, é necessária alguma outra coluna.
Rick James

6

Aqui estão duas sugestões. Primeiro, se o mysql suporta ROW_NUMBER (), é muito simples:

WITH Ranked AS (
  SELECT Id, Name, OtherColumns,
    ROW_NUMBER() OVER (
      PARTITION BY Name
      ORDER BY Id DESC
    ) AS rk
  FROM messages
)
  SELECT Id, Name, OtherColumns
  FROM messages
  WHERE rk = 1;

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:

SELECT
  Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
  SELECT * FROM messages as M2
  WHERE M2.Name = messages.Name
  AND M2.Id > messages.Id
)

Em outras palavras, selecione as mensagens nas quais não há mensagens de identificação posterior com o mesmo nome.


8
O MySQL não suporta ROW_NUMBER () ou CTE.
22977 Bill Karwin

1
O MySQL 8.0 (e o MariaDB 10.2) agora suportam ROW_NUMBER()e CTEs.
Rick James

6

Ainda não testei com banco de dados grande, mas acho que isso poderia ser mais rápido do que juntar tabelas:

SELECT *, Max(Id) FROM messages GROUP BY Name

14
Isso retorna dados arbitrários. Em outras palavras, as colunas retornadas podem não pertencer ao registro com MAX (Id).
prejudicar

Útil para selecionar o ID máximo de um conjunto de registros com a condição WHERE: "SELECT Max (Id) FROM Prod WHERE Pn = '" + Pn + "'" Retorna o ID máximo de um conjunto de registros com o mesmo Pn.In c # uso reader.GetString (0) para obter o resultado
Nicola

5

Aqui está outra maneira de obter o último registro relacionado usando GROUP_CONCATcom a ordem de e SUBSTRING_INDEXescolher um dos registros da lista

SELECT 
  `Id`,
  `Name`,
  SUBSTRING_INDEX(
    GROUP_CONCAT(
      `Other_Columns` 
      ORDER BY `Id` DESC 
      SEPARATOR '||'
    ),
    '||',
    1
  ) Other_Columns 
FROM
  messages 
GROUP BY `Name` 

A consulta acima agrupará todos os Other_Columnsque estão no mesmo Namegrupo e usar ORDER BY id DESCjuntará todos os Other_Columnsem um grupo específico em ordem decrescente com o separador fornecido no meu caso que eu usei ||, usandoSUBSTRING_INDEX sobre esta lista escolheremos o primeiro

Fiddle Demo


Esteja ciente de que group_concat_max_lenlimita quantas linhas você pode manipular.
Rick James

5

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:

CREATE TABLE temperature(
  id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
  groupID INT UNSIGNED NOT NULL, 
  recordedTimestamp TIMESTAMP NOT NULL, 
  recordedValue INT NOT NULL,
  INDEX groupIndex(groupID, recordedTimestamp), 
  PRIMARY KEY (id)
);

CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id)); 

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:

SELECT 
  last(t1.id) AS id, 
  t1.groupID, 
  last(t1.recordedTimestamp) AS recordedTimestamp, 
  last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;

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

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT t2.id
  FROM temperature t2 
  WHERE t2.groupID = g.id
  ORDER BY t2.recordedTimestamp DESC, t2.id DESC
  LIMIT 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

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
INNER JOIN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
) t5 ON t5.id = t1.id;

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

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
WHERE t1.id IN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
)
ORDER BY t1.groupID;

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

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT max(t2.id)
  FROM temperature t2 
  WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
      SELECT max(t3.recordedTimestamp)
      FROM temperature t3 
      WHERE t3.groupID = g.id
    )
);

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

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
  SELECT 
    t2.id, 
    t2.groupID, 
    t2.recordedTimestamp, 
    t2.recordedValue, 
    row_number() OVER (
      PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
    ) AS rowNumber
  FROM selected_group g 
  INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;

Isso examinou 6.017.808 linhas e levou ~ 4,2 segundos no 8.0.4-rc

Exemplo 6

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM (
  SELECT 
    last_value(t2.id) OVER w AS id, 
    t2.groupID, 
    last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp, 
    last_value(t2.recordedValue) OVER w AS recordedValue
  FROM selected_group g
  INNER JOIN temperature t2 ON t2.groupID = g.id
  WINDOW w AS (
    PARTITION BY t2.groupID 
    ORDER BY t2.recordedTimestamp, t2.id 
    RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  )
) t1
GROUP BY t1.groupID;

Este examinou 6.017.908 linhas e levou ~ 17,5 segundos no 8.0.4-rc

Exemplo 7

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2 
  ON t2.groupID = g.id 
  AND (
    t2.recordedTimestamp > t1.recordedTimestamp 
    OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
  )
WHERE t2.id IS NULL
ORDER BY t1.groupID;

Este estava levando uma eternidade, então eu tive que matá-lo.


Este é um problema diferente. E a solução é uma enorme consulta UNION ALL.
Paul Spiegel

@PaulSpiegel Acho que você está brincando sobre a enorme UNION ALL. Além do fato de que seria necessário conhecer todos os grupos selecionados com antecedência e que, com 2.000 grupos selecionados, seria uma consulta incrivelmente grande, o desempenho seria ainda pior do que o exemplo mais rápido acima, portanto, não, isso não seria um solução.
Yoseph

Estou falando sério. Eu testei isso no passado com algumas centenas de grupos. Quando você precisa lidar com laços em grandes grupos, UNION ALL é a única maneira no MySQL para forçar um plano de execução ideal. 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 exceda max_allowed_packet, o padrão é 4 MB no MySQL 5.7.
Paul Spiegel

5

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


4
SELECT 
  column1,
  column2 
FROM
  table_name 
WHERE id IN 
  (SELECT 
    MAX(id) 
  FROM
    table_name 
  GROUP BY column1) 
ORDER BY column1 ;

Você poderia elaborar um pouco sua resposta? Por que sua consulta é preferível à consulta original do Vijays?
Janfoeh 04/04

4

Aqui está a minha solução:

SELECT 
  DISTINCT NAME,
  MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES 
FROM MESSAGE;

Isso não retorna a mensagem mais recente por nome. E é apenas uma versão complicada demais SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel

Além disso, esta formulação é grosseiramente ineficiente.
Rick James

3

Tente o seguinte:

SELECT jos_categories.title AS name,
       joined .catid,
       joined .title,
       joined .introtext
FROM   jos_categories
       INNER JOIN (SELECT *
                   FROM   (SELECT `title`,
                                  catid,
                                  `created`,
                                  introtext
                           FROM   `jos_content`
                           WHERE  `sectionid` = 6
                           ORDER  BY `id` DESC) AS yes
                   GROUP  BY `yes`.`catid` DESC
                   ORDER  BY `yes`.`created` DESC) AS joined
         ON( joined.catid = jos_categories.id )  

3

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:

SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId

Este o mais rápido que eu encontrei
CORSAIR

3

Você pode ver aqui também.

http://sqlfiddle.com/#!9/ef42b/9

PRIMEIRA SOLUÇÃO

SELECT d1.ID,Name,City FROM Demo_User d1
INNER JOIN
(SELECT MAX(ID) AS ID FROM Demo_User GROUP By NAME) AS P ON (d1.ID=P.ID);

SEGUNDA SOLUÇÃO

SELECT * FROM (SELECT * FROM Demo_User ORDER BY ID DESC) AS T GROUP BY NAME ;

3
SELECT * FROM table_name WHERE primary_key IN (SELECT MAX(primary_key) FROM table_name GROUP BY column_name )

3

**

Olá, esta consulta pode ajudar:

**

SELECT 
  *
FROM 
  message 

WHERE 
  `Id` IN (
    SELECT 
      MAX(`Id`) 
    FROM 
      message 
    GROUP BY 
      `Name`
  ) 
ORDER BY 
   `Id` DESC

2

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.

DELETE FROM messages WHERE id NOT IN
 (SELECT m1.id  
 FROM messages m1 LEFT JOIN messages m2  
 ON (m1.name = m2.name AND m1.id < m2.id)  
 WHERE m2.id IS NULL)

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:

DROP TABLE IF EXISTS UniqueIDs; 
CREATE Temporary table UniqueIDs (id Int(11)); 

INSERT INTO UniqueIDs 
    (SELECT T1.ID FROM Table T1 LEFT JOIN Table T2 ON 
    (T1.Field1 = T2.Field1 AND T1.Field2 = T2.Field2 #Comparison Fields  
    AND T1.ID < T2.ID) 
    WHERE T2.ID IS NULL); 

DELETE FROM Table WHERE id NOT IN (SELECT ID FROM UniqueIDs);

2

A consulta abaixo funcionará bem conforme sua pergunta.

SELECT M1.* 
FROM MESSAGES M1,
(
 SELECT SUBSTR(Others_data,1,2),MAX(Others_data) AS Max_Others_data
 FROM MESSAGES
 GROUP BY 1
) M2
WHERE M1.Others_data = M2.Max_Others_data
ORDER BY Others_data;

2

Se você deseja a última linha para cada um Name, é possível atribuir um número de linha a cada grupo de linhas por Namee ordem Idem ordem decrescente.

INQUERIR

SELECT t1.Id, 
       t1.Name, 
       t1.Other_Columns
FROM 
(
     SELECT Id, 
            Name, 
            Other_Columns,
    (
        CASE Name WHEN @curA 
        THEN @curRow := @curRow + 1 
        ELSE @curRow := 1 AND @curA := Name END 
    ) + 1 AS rn 
    FROM messages t, 
    (SELECT @curRow := 0, @curA := '') r 
    ORDER BY Name,Id DESC 
)t1
WHERE t1.rn = 1
ORDER BY t1.Id;

SQL Fiddle


2

Que tal agora:

SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;

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


1

Se o desempenho é realmente sua preocupação, você pode introduzir uma nova coluna na tabela chamada IsLastInGroupdo 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:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Algumas tabelas no Moodle possuem uma coluna de flag como esta.
Lawrence


0

Você pode agrupar contando e também obter o último item do grupo, como:

SELECT 
    user,
    COUNT(user) AS count,
    MAX(id) as last
FROM request 
GROUP BY user

0

A esperança abaixo da consulta Oracle pode ajudar:

WITH Temp_table AS
(
    Select id, name, othercolumns, ROW_NUMBER() over (PARTITION BY name ORDER BY ID 
    desc)as rank from messages
)
Select id, name,othercolumns from Temp_table where rank=1

0

Outra abordagem:

Encontre a propriedade com o max m2_price dentro de cada programa (n propriedades em 1 programa):

select * from properties p
join (
    select max(m2_price) as max_price 
    from properties 
    group by program_id
) p2 on (p.program_id = p2.program_id)
having p.m2_price = max_price
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.