PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Obtenha n categorias agrupadas e some outras em uma


A dificuldade específica aqui:Consultas com uma ou mais funções agregadas no SELECT lista e não GROUP BY cláusula produz exatamente uma linha, mesmo que nenhuma linha seja encontrada na tabela subjacente.

Não há nada que você possa fazer no WHERE cláusula para suprimir essa linha. Você deve excluir tal linha após o fato , ou seja, no HAVING cláusula ou em uma consulta externa.

Por documentação:

Se uma consulta contiver chamadas de função agregadas, mas não GROUP BY cláusula,o agrupamento ainda ocorre:o resultado é uma única linha de grupo (ou talvez norows, se a única linha for eliminada por HAVING ). O mesmo é verdadeiro se contiver um HAVING cláusula, mesmo sem chamadas de função agregada ou GROUP BY cláusula.

Deve-se notar que adicionar um GROUP BY cláusula com apenas uma expressão constante (que de outra forma é completamente inútil!) também funciona. Veja o exemplo abaixo. Mas prefiro não usar esse truque, mesmo que seja curto, barato e simples, porque dificilmente é óbvio o que ele faz.

A consulta a seguir precisa apenas de uma varredura de tabela única e retorna as 7 principais categorias ordenadas por contagem. Se (e somente se ) existem mais categorias, o resto é resumido em 'Outros':
WITH cte AS (
   SELECT categoryid, count(*) AS data
        , row_number() OVER (ORDER BY count(*) DESC, categoryid) AS rn
   FROM   contents
   GROUP  BY 1
   )
(  -- parentheses required again
SELECT categoryid, COALESCE(ca.name, 'Unknown') AS label, data
FROM   cte
LEFT   JOIN category ca ON ca.id = cte.categoryid
WHERE  rn <= 7
ORDER  BY rn
)
UNION ALL
SELECT NULL, 'Others', sum(data)
FROM   cte
WHERE  rn > 7         -- only take the rest
HAVING count(*) > 0;  -- only if there actually is a rest
-- or: HAVING  sum(data) > 0

  • Você precisa desempate se várias categorias puderem ter a mesma contagem na 7ª/8ª classificação. No meu exemplo, categorias com o menor categoryid vencer tal corrida.

  • Os parênteses são necessários para incluir um LIMIT ou ORDER BY cláusula para uma perna individual de um UNION inquerir.

  • Você só precisa se juntar à tabela category para as 7 melhores categorias. E geralmente é mais barato agregar primeiro e juntar mais tarde neste cenário. Portanto, não participe da consulta base no CTE (expressão de tabela comum) chamado cte , junte-se apenas no primeiro SELECT da UNION consulta, que é mais barato.

  • Não sei por que você precisa do COALESCE . Se você tiver uma chave estrangeira em contents.categoryid para category.id e ambos contents.categoryid e category.name são definidos NOT NULL (como eles provavelmente deveriam ser), então você não precisa disso.

O ímpar GROUP BY true


Isso também funcionaria:
...

UNION ALL
SELECT NULL , 'Others', sum(data)
FROM   cte
WHERE  rn > 7
GROUP BY true; 

E ainda recebo planos de consulta um pouco mais rápidos. Mas é um hack meio estranho...

Fiddle SQL demonstrando tudo.

Resposta relacionada com mais explicações para o UNION ALL / LIMIT técnica:
  • Somar os resultados de algumas consultas e encontrar os 5 principais em SQL