PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Ottieni n categorie raggruppate e somma le altre in una

La difficoltà specifica qui:query con una o più funzioni aggregate in SELECT list e nessun GROUP BY La clausola produce esattamente una riga, anche se non viene trovata alcuna riga nella tabella sottostante.

Non c'è niente che puoi fare in WHERE clausola per sopprimere quella riga. Devi escludere tale riga dopo il fatto , cioè nel HAVING clausola o in una query esterna.

Per documentazione:

Se una query contiene chiamate di funzioni aggregate, ma non GROUP BY clausola, il raggruppamento si verifica ancora:il risultato è una singola riga di gruppo (o forse nessuna riga, se la singola riga viene poi eliminata da HAVING ). Lo stesso vale se contiene un HAVING clausola, anche senza chiamate aggregatefunction o GROUP BY clausola.

Va notato che l'aggiunta di un GROUP BY Anche la clausola con solo un'espressione costante (che altrimenti sarebbe completamente inutile!) funziona. Vedi esempio sotto. Ma preferirei non usare quel trucco, anche se è breve, economico e semplice, perché non è affatto ovvio cosa faccia.

La query seguente richiede solo una analisi di una singola tabella e restituisce le prime 7 categorie ordinate per conteggio. Se (e solo se ) ci sono più categorie, il resto è riassunto in 'Altri':

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
  • Devi rompere i pareggi se più categorie possono avere lo stesso conteggio nel 7°/8° grado. Nel mio esempio, le categorie con il categoryid più piccolo vincere una gara del genere.

  • Le parentesi sono necessarie per includere un LIMIT o ORDER BY clausola a una singola gamba di una UNION interrogazione.

  • Devi solo iscriverti alla tabella category per le prime 7 categorie. Ed è generalmente più economico aggregare prima e unirsi successivamente in questo scenario. Quindi non unirti alla query di base nella CTE (espressione di tabella comune) denominata cte , partecipa solo al primo SELECT della UNION query, è più economico.

  • Non sono sicuro del motivo per cui hai bisogno del COALESCE . Se disponi di una chiave esterna da contents.categoryid a category.id ed entrambi contents.categoryid e category.name sono definiti NOT NULL (come probabilmente dovrebbero essere), allora non ne hai bisogno.

Lo dispari GROUP BY true

Funzionerebbe anche questo:

...

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

E ottengo anche piani di query leggermente più veloci. Ma è un trucco piuttosto strano...

SQL Fiddle dimostrando tutto.

Risposta correlata con ulteriori spiegazioni per UNION ALL / LIMIT tecnica:

  • Somma i risultati di alcune query e poi trova le prime 5 in SQL