Ordem do MySQL antes do grupo por


243

Há muitas perguntas semelhantes a serem encontradas aqui, mas acho que nenhuma resposta é adequada.

Vou continuar com a pergunta mais popular atual e usar o exemplo deles, se estiver tudo bem.

A tarefa nesta instância é obter a última publicação de cada autor no banco de dados.

A consulta de exemplo produz resultados inutilizáveis, pois nem sempre é a postagem mais recente retornada.

SELECT wp_posts.* FROM wp_posts
    WHERE wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
    GROUP BY wp_posts.post_author           
    ORDER BY wp_posts.post_date DESC

A resposta atual aceita é

SELECT
    wp_posts.*
FROM wp_posts
WHERE
    wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
HAVING wp_posts.post_date = MAX(wp_posts.post_date) <- ONLY THE LAST POST FOR EACH AUTHOR
ORDER BY wp_posts.post_date DESC

Infelizmente, essa resposta é pura e simplesmente errada e, em muitos casos, produz resultados menos estáveis ​​que a consulta original.

Minha melhor solução é usar uma subconsulta do formulário

SELECT wp_posts.* FROM 
(
    SELECT * 
    FROM wp_posts
    ORDER BY wp_posts.post_date DESC
) AS wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author 

Minha pergunta é simples: existe alguma maneira de ordenar linhas antes de agrupar sem recorrer a uma subconsulta?

Edit : Esta questão foi uma continuação de outra questão e as especificidades da minha situação são ligeiramente diferentes. Você pode (e deve) assumir que também existe um wp_posts.id que é um identificador exclusivo para essa postagem específica.


2
Como você mencionou nos comentários das respostas dadas, pode ser possível ter algumas postagens com o mesmo carimbo de data / hora. Nesse caso, dê um exemplo com dados e o resultado esperado. E, por favor, descreva por que você espera esse resultado. post_authore post_datenão são o suficiente para obter uma linha única, então tem que haver mais para obter uma linha única perpost_author
Sir Rufo

@SirRufo Você está certo, eu adicionei uma edição para você.
Rob Forrest

There are plenty of similar questions to be found on here but I don't think that any answer the question adequately.É para isso que servem as recompensas.
Lightness Races em órbita

@LightnessRacesinOrbit, se a pergunta atual já tiver uma resposta aceita que, na minha opinião, esteja errada, o que você sugeriria fazer?
Rob Forrest

1
Querendo saber por que você aceitou uma resposta que usa uma subconsulta - quando sua pergunta claramente pergunta ... "" Existe alguma maneira de ordenar linhas antes de agrupar sem recorrer a uma subconsulta? "???
TV-C-15

Respostas:


373

Usar uma ORDER BYem uma subconsulta não é a melhor solução para esse problema.

A melhor solução para obter o max(post_date)autor é usar uma subconsulta para retornar a data máxima e associá-la à sua tabela na post_authordata e na data máxima.

A solução deve ser:

SELECT p1.* 
FROM wp_posts p1
INNER JOIN
(
    SELECT max(post_date) MaxPostDate, post_author
    FROM wp_posts
    WHERE post_status='publish'
       AND post_type='post'
    GROUP BY post_author
) p2
  ON p1.post_author = p2.post_author
  AND p1.post_date = p2.MaxPostDate
WHERE p1.post_status='publish'
  AND p1.post_type='post'
order by p1.post_date desc

Se você tiver os seguintes dados de amostra:

CREATE TABLE wp_posts
    (`id` int, `title` varchar(6), `post_date` datetime, `post_author` varchar(3))
;

INSERT INTO wp_posts
    (`id`, `title`, `post_date`, `post_author`)
VALUES
    (1, 'Title1', '2013-01-01 00:00:00', 'Jim'),
    (2, 'Title2', '2013-02-01 00:00:00', 'Jim')
;

A subconsulta retornará a data máxima e o autor de:

MaxPostDate | Author
2/1/2013    | Jim

Então, como você está juntando isso de volta à tabela, nos dois valores, você retornará os detalhes completos dessa postagem.

Veja SQL Fiddle com demonstração .

Para expandir meus comentários sobre o uso de uma subconsulta para retornar com precisão esses dados.

O MySQL não o força a GROUP BYtodas as colunas que você inclui na SELECTlista. Como resultado, se você apenas GROUP BYuma coluna, mas retornar 10 colunas no total, não há garantia de que os outros valores da coluna que pertencem à post_authorque são retornados. Se a coluna não estiver no GROUP BYMySQL, escolha qual valor deve ser retornado.

O uso da subconsulta com a função agregada garantirá que o autor e a postagem corretos sejam retornados sempre.

Como uma observação lateral, enquanto o MySQL permite que você use um ORDER BYem uma subconsulta e aplique a GROUP BYa nem todas as colunas da SELECTlista, esse comportamento não é permitido em outros bancos de dados, incluindo o SQL Server.


4
Vejo o que você fez lá, mas isso simplesmente retorna a data em que a postagem mais recente foi feita, não a linha inteira da postagem mais recente.
Rob Forrest

1
@RobForrest é isso que a associação faz. Você retorna a data de postagem mais recente na subconsulta por autor e, em seguida, ingressa novamente wp_postsnas duas colunas para obter a linha completa.
Taryn

7
@RobForrest Por um lado, quando você aplica a GROUP BYapenas uma coluna, não há garantia de que os valores nas outras colunas estejam sempre corretos. Infelizmente, o MySQL permite que esse tipo de SELECT / GROUP ocorra com outros produtos. Segundo, a sintaxe do uso de uma ORDER BYem uma subconsulta enquanto permitida no MySQL não é permitida em outros produtos de banco de dados, incluindo o SQL Server. Você deve usar uma solução que retorne o resultado adequado toda vez que for executado.
Taryn

2
Para a escala, o composto INDEX(post_author, post_date)é importante.
21715 Rick Rick

1
@ jtcotton63 É verdade, mas se você colocar post_idsua consulta interna, tecnicamente também deve agrupá-la, o que provavelmente distorcerá seus resultados.
Taryn

20

Sua solução utiliza uma extensão da cláusula GROUP BY que permite agrupar por alguns campos (neste caso, apenas post_author):

GROUP BY wp_posts.post_author

e selecione colunas não agregadas:

SELECT wp_posts.*

que não estão listados no grupo por cláusula ou que não são usados ​​em uma função agregada (MIN, MAX, COUNT, etc.).

Uso correto da extensão à cláusula GROUP BY

Isso é útil quando todos os valores de colunas não agregadas são iguais para cada linha.

Por exemplo, suponha que você tenha uma tabela GardensFlowers( namedo jardim, flowerque cresce no jardim):

INSERT INTO GardensFlowers VALUES
('Central Park',       'Magnolia'),
('Hyde Park',          'Tulip'),
('Gardens By The Bay', 'Peony'),
('Gardens By The Bay', 'Cherry Blossom');

e você deseja extrair todas as flores que crescem em um jardim, onde várias flores crescem. Então você tem que usar uma subconsulta, por exemplo, você pode usar isto:

SELECT GardensFlowers.*
FROM   GardensFlowers
WHERE  name IN (SELECT   name
                FROM     GardensFlowers
                GROUP BY name
                HAVING   COUNT(DISTINCT flower)>1);

Se você precisar extrair todas as flores que são as únicas no jardim, basta alterar a condição HAVING para HAVING COUNT(DISTINCT flower)=1, mas o MySql também permite que você use isso:

SELECT   GardensFlowers.*
FROM     GardensFlowers
GROUP BY name
HAVING   COUNT(DISTINCT flower)=1;

sem subconsulta, não SQL padrão, mas mais simples.

Uso incorreto da extensão à cláusula GROUP BY

Mas o que acontece se você selecionar colunas não agregadas que não são iguais para cada linha? Qual é o valor que o MySql escolhe para essa coluna?

Parece que o MySql sempre escolhe o PRIMEIRO valor que encontra.

Para garantir que o primeiro valor encontrado seja exatamente o valor desejado, aplique a GROUP BYa uma consulta ordenada, daí a necessidade de usar uma subconsulta. Você não pode fazer isso de outra maneira.

Dado que o MySql sempre escolhe a primeira linha que encontra, você está classificando corretamente as linhas antes do GROUP BY. Infelizmente, se você ler atentamente a documentação, perceberá que essa suposição não é verdadeira.

Ao selecionar colunas não agregadas que nem sempre são iguais, o MySql é livre para escolher qualquer valor, portanto o valor resultante que ele realmente mostra é indeterminado .

Vejo que esse truque para obter o primeiro valor de uma coluna não agregada é muito usado, e geralmente / quase sempre funciona, eu também o uso às vezes (por meu próprio risco). Mas como não está documentado, você não pode confiar nesse comportamento.

Este link (obrigado ypercube!) O truque GROUP BY foi otimizado para longe, mostra uma situação em que a mesma consulta retorna resultados diferentes entre o MySql e o MariaDB, provavelmente por causa de um mecanismo de otimização diferente.

Portanto, se esse truque funcionar, é apenas uma questão de sorte.

A resposta aceita na outra pergunta parece errada para mim:

HAVING wp_posts.post_date = MAX(wp_posts.post_date)

wp_posts.post_dateé uma coluna não agregada e seu valor será oficialmente indeterminado, mas provavelmente será o primeiro post_dateencontrado. Porém, como o truque GROUP BY é aplicado a uma tabela não ordenada, não há certeza de qual é a primeira post_dateencontrada.

Provavelmente retornará postagens que são as únicas postagens de um único autor, mas mesmo isso nem sempre é certo.

Uma possível solução

Eu acho que isso poderia ser uma solução possível:

SELECT wp_posts.*
FROM   wp_posts
WHERE  id IN (
  SELECT max(id)
  FROM wp_posts
  WHERE (post_author, post_date) = (
    SELECT   post_author, max(post_date)
    FROM     wp_posts
    WHERE    wp_posts.post_status='publish'
             AND wp_posts.post_type='post'
    GROUP BY post_author
  ) AND wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
  GROUP BY post_author
)

Na consulta interna, estou retornando a data máxima de postagem para cada autor. Então, estou levando em consideração o fato de que o mesmo autor poderia teoricamente ter duas postagens ao mesmo tempo, então estou obtendo apenas o ID máximo. E então eu estou retornando todas as linhas que têm esses IDs máximos. Isso pode ser feito mais rapidamente usando junções em vez da cláusula IN.

(Se você tem certeza de que IDestá aumentando apenas e ID1 > ID2também significa isso post_date1 > post_date2, a consulta pode ser muito mais simples, mas não tenho certeza se esse é o caso).


Essa extension to GROUP Byé uma leitura interessante, obrigado por isso.
Rob Forrest

2
Um exemplo em que falha: o truque GROUP BY foi otimizado
ypercubeᵀᴹ

Colunas não agregadas em expressões selecionadas com GROUP BY não funcionam mais por padrão no MySQL 5.7: stackoverflow.com/questions/34115174/… . Qual IMHO é muito mais seguro e força algumas pessoas a escrever consultas mais eficientes.
rink.attendant.6

Esta resposta não usa uma subconsulta? O Pôster original não está solicitando uma solução que NÃO use uma subconsulta?
TV-C-15

1
@ TV-C-15, o problema está no recurso à subconsulta, e estou explicando por que o recurso a uma subconsulta não funciona. Mesmo a resposta aceita usa uma subconsulta, mas ele começa explicando por que recorrer é uma má idéia ( Usando um ORDER BY em uma subconsulta não é a melhor solução para este problema )
fthiella

9

O que você vai ler é bastante hacky, então não tente fazer isso em casa!

No SQL em geral, a resposta para sua pergunta é NÃO , mas devido ao modo descontraído do GROUP BY(mencionado por @bluefeet ), a resposta é SIM no MySQL.

Suponha que você tenha um índice BTREE em (post_status, post_type, post_author, post_date). Como é o índice embaixo do capô?

(post_status = 'publicar', post_type = 'post', post_author = 'usuário A', post_date = '2012-12-01') (post_status = 'publicar', post_type = 'post', post_author = 'usuário A', post_date = '2012-12-31') (post_status = 'publique', post_type = 'post', post_author = 'usuário B', post_date = '2012-10-01') (post_status = 'publique', post_type = ' post ', post_author =' usuário B ', post_date =' 2012-12-01 ')

Ou seja, os dados são classificados por todos esses campos em ordem crescente.

Quando você faz um, GROUP BYpor padrão, ele classifica os dados pelo campo de agrupamento ( post_authorno nosso caso; post_status, post_type são requeridos pela WHEREcláusula) e, se houver um índice correspondente, os dados de cada primeiro registro serão coletados em ordem crescente. Essa é a consulta que buscará o seguinte (a primeira postagem para cada usuário):

(post_status = 'publicar', post_type = 'post', post_author = 'usuário A', post_date = '2012-12-01') (post_status = 'publicar', post_type = 'post', post_author = 'usuário B', post_date = '01/10/2012')

Mas GROUP BYno MySQL permite que você especifique a ordem explicitamente. E quando você solicita post_userem ordem decrescente, ele percorre nosso índice na ordem oposta, ainda obtendo o primeiro registro para cada grupo que é realmente o último.

Isso é

...
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC

nos dará

(post_status = 'publicar', post_type = 'post', post_author = 'usuário B', post_date = '2012-12-01') (post_status = 'publicar', post_type = 'post', post_author = 'usuário A', post_date = '31/12/2012')

Agora, quando você ordena os resultados do agrupamento por post_date, obtém os dados desejados.

SELECT wp_posts.*
FROM wp_posts
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC
ORDER BY wp_posts.post_date DESC;

NB :

Não é isso que eu recomendaria para essa consulta específica. Nesse caso, eu usaria uma versão ligeiramente modificada do que o @bluefeet sugere. Mas essa técnica pode ser muito útil. Dê uma olhada na minha resposta aqui: Recuperando o último registro em cada grupo

Armadilhas : As desvantagens da abordagem são que

  • o resultado da consulta depende do índice, o que é contrário ao espírito do SQL (os índices devem apenas acelerar as consultas);
  • O índice não sabe nada sobre sua influência na consulta (você ou outra pessoa no futuro pode achar que o índice consome muitos recursos e alterá-lo de alguma forma, interrompendo os resultados da consulta, não apenas seu desempenho)
  • se você não entender como a consulta funciona, provavelmente esquecerá a explicação em um mês e a consulta confundirá você e seus colegas.

A vantagem é o desempenho em casos difíceis. Nesse caso, o desempenho da consulta deve ser o mesmo da consulta do @ bluefeet, devido à quantidade de dados envolvidos na classificação (todos os dados são carregados em uma tabela temporária e depois classificados; btw, sua consulta também requer o (post_status, post_type, post_author, post_date)índice) .

O que eu sugeriria :

Como eu disse, essas consultas fazem com que o MySQL perca tempo classificando quantidades potencialmente enormes de dados em uma tabela temporária. Caso você precise de paginação (ou seja, LIMIT está envolvido), a maioria dos dados é descartada. O que eu faria é minimizar a quantidade de dados classificados: isto é, ordenar e limitar um mínimo de dados na subconsulta e, em seguida, ingressar novamente na tabela inteira.

SELECT * 
FROM wp_posts
INNER JOIN
(
  SELECT max(post_date) post_date, post_author
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) p2 USING (post_author, post_date)
WHERE post_status='publish' AND post_type='post';

A mesma consulta usando a abordagem descrita acima:

SELECT *
FROM (
  SELECT post_id
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author DESC
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) as ids
JOIN wp_posts USING (post_id);

Todas essas consultas com seus planos de execução no SQLFiddle .


Essa é uma técnica interessante que você tem por aí. Duas coisas: você diz não tente fazer isso em casa, quais são as possíveis armadilhas? segundo, você menciona uma versão ligeiramente modificada da resposta da bluefeet, o que seria?
Rob Forrest

Obrigado por isso, é interessante ver alguém atacando o problema de uma maneira diferente. Como meu conjunto de dados não está nem perto de suas linhas de mais de 18 milhões, não acho que o desempenho seja tão crucial quanto a capacidade de manutenção, por isso acho que suas opções posteriores provavelmente são mais adequadas. Eu gosto da ideia do limite no interior da subconsulta.
Rob Forrest

8

Tente este. Basta obter a lista das últimas datas de postagem de cada autor . É isso aí

SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post' AND wp_posts.post_date IN(SELECT MAX(wp_posts.post_date) FROM wp_posts GROUP BY wp_posts.post_author) 

@ Rob Forrest, verifique minha solução. Resolve sua pergunta, espero!
precisa saber é o seguinte

1
Sinto muito, não acho que isso funcionaria. Por exemplo, se o autor 1 e o autor 2 publicarem algo em 01/02/13 e, em seguida, o autor 2 postar algo novo em 02/02/13, todas as três postagens serão retornadas. Sim, o campo datetime inclui o horário, portanto a situação é menos provável, mas de maneira alguma é garantida em um conjunto de dados grande o suficiente.
Rob Forrest

+1 para usar o post_date IN (select max(...) ...). Isso é mais eficiente do que fazer um grupo em uma sub-seleção, consulte dev.mysql.com/doc/refman/5.6/en/subquery-optimization.html #
Seaux

só para esclarecer, isso é apenas mais ideal se você tiver post_author indexado.
Seaux

1
IN ( SELECT ... )é muito menos eficiente que o equivalente a JOIN.
21976 Rick Rick

3

Não. Não faz sentido ordenar os registros antes do agrupamento, pois o agrupamento irá alterar o conjunto de resultados. O caminho da subconsulta é o caminho preferido. Se isso estiver indo muito devagar, você terá que alterar o design da sua tabela, por exemplo, armazenando o ID da última postagem de cada autor em uma tabela separada ou introduzir uma coluna booleana indicando para cada autor qual é a última da postagem. 1.


Dennish, como você responderia aos comentários do Bluefeet de que esse tipo de consulta não está com a sintaxe correta do SQL e, portanto, não é portátil nas plataformas de banco de dados? Há também preocupações de que não haja garantia de que isso produziria os resultados corretos sempre.
Rob Forrest

2

Basta usar a função max e a função de grupo

    select max(taskhistory.id) as id from taskhistory
            group by taskhistory.taskid
            order by taskhistory.datum desc

3
E se aquele com o ID mais alto não for o post mais recente? Um exemplo disso pode ser o fato de o autor ter mantido seu post em rascunho por um longo período de tempo antes de publicá-lo.
Rob Forrest

0

Apenas para recapitular, a solução padrão usa uma subconsulta não correlacionada e fica assim:

SELECT x.*
  FROM my_table x
  JOIN (SELECT grouping_criteria,MAX(ranking_criterion) max_n FROM my_table GROUP BY grouping_criteria) y
    ON y.grouping_criteria = x.grouping_criteria
   AND y.max_n = x.ranking_criterion;

Se você estiver usando uma versão antiga do MySQL ou um conjunto de dados bastante pequeno, poderá usar o seguinte método:

SELECT x.*
  FROM my_table x
  LEFT
  JOIN my_table y
    ON y.joining_criteria = x.joining_criteria
   AND y.ranking_criteria < x.ranking_criteria
 WHERE y.some_non_null_column IS NULL;  

Quando você diz a versão antiga, em qual versão do MySQL isso funcionaria? E desculpe não, o conjunto de dados é bastante grande no meu exemplo.
Rob Forrest

Funcionará (lentamente) em qualquer versão. Versões anteriores não podem usar subconsultas.
morango

Sim, o método 2 (a versão que tentei daqui ) não funcionará em um grande conjunto de dados (milhões de linhas), gera um erro de conexão perdida . O método 1 leva ~ 15 segundos para executar uma consulta. Inicialmente, eu queria evitar o uso de consultas aninhadas, mas isso me fez reconsiderar. Obrigado!
Aexl

@TheSexiestManinJamaica Sim. Não mudou muito em 3,5 anos. Supondo que uma consulta seja eficiente em si mesma, o tempo que a consulta leva para ser executado depende muito do tamanho do conjunto de dados, da organização dos índices e do hardware disponível.
Strawberry

-1

** Subconsultas podem ter um impacto ruim no desempenho quando usadas com grandes conjuntos de dados **

Consulta original

SELECT wp_posts.*
FROM   wp_posts
WHERE  wp_posts.post_status = 'publish'
       AND wp_posts.post_type = 'post'
GROUP  BY wp_posts.post_author
ORDER  BY wp_posts.post_date DESC; 

Consulta modificada

SELECT p.post_status,
       p.post_type,
       Max(p.post_date),
       p.post_author
FROM   wp_posts P
WHERE  p.post_status = "publish"
       AND p.post_type = "post"
GROUP  BY p.post_author
ORDER  BY p.post_date; 

porque eu estou usando maxno select clause==> max(p.post_date)é possível evitar consultas sub-select e ordenar pela coluna max após o grupo por.


1
Isso realmente retorna o post_date mais recente por autor, mas não há garantia de que o restante dos dados retornados esteja relacionado à postagem com o post_date mais recente.
Rob Forrest

@RobForrest -> Não entendo o porquê? é uma boa ideia elaborar sua resposta e simplesmente jogar fora reivindicações. Tanto quanto eu entendo, é garantido que os dados estejam relacionados, pois uso a cláusula where para filtrar os dados relacionados.
guykaplan

1
Até certo ponto, você está totalmente correto, cada um dos quatro campos que você seleciona se relacionará com esse máximo de pós-data, mas isso não responde à pergunta que foi feita. Por exemplo, se você adicionou post_id ou o conteúdo da postagem, essas colunas não terão a garantia de pertencer ao mesmo registro que a data máxima. Para obter sua consulta acima e retornar o restante dos detalhes da postagem, você teria que executar uma segunda consulta. Se a pergunta era sobre encontrar a data da postagem mais recente, sim, sua resposta seria adequada.
Rob Forrest

@guykaplan, as subconsultas não são lentas. O tamanho do conjunto de dados não importa. Depende de como você o usa. Veja percona.com/blog/2010/03/18/when-the-subselect-runs-faster
Pacerier

@ Pacerier: o artigo realmente mostra como você pode obter benefícios de desempenho de subconsultas, mas eu adoraria ver você converter o cenário especificado para ter um desempenho melhor. e O tamanho dos dados é importante. Novamente, no artigo fornecido, você está assumindo que existe apenas uma tabela para trabalhar. o tamanho dos dados não é do tamanho da linha, é do tamanho da complexidade. Dito isto, se você estiver trabalhando com uma subconsulta de tabela muito grande (poucas tabelas envolvidas) poderá ter um desempenho muito melhor.
guykaplan

-4

Primeiro, não use * no select, afeta seu desempenho e dificulta o uso do grupo e a ordem de. Tente esta consulta:

SELECT wp_posts.post_author, wp_posts.post_date as pdate FROM wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author           
ORDER BY pdate DESC

Quando você não especifica a tabela em ORDER BY, apenas o alias, eles ordenam o resultado da seleção.


Ignore os select * 's, eles são concisos neste exemplo. Sua resposta é exatamente a mesma do primeiro exemplo que dei.
Rob Forrest

O alias não afeta em qual linha é retornada nem na classificação dos resultados.
Rob Forrest
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.