PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Obtenez n catégories groupées et additionnez les autres en une seule

La difficulté spécifique ici :Requêtes avec une ou plusieurs fonctions d'agrégation dans le SELECT list et pas de GROUP BY clause produit exactement une ligne, même si aucune ligne n'est trouvée dans la table sous-jacente.

Vous ne pouvez rien faire dans WHERE clause pour supprimer cette ligne. Vous devez exclure une telle ligne après coup , c'est-à-dire dans le HAVING clause, ou dans une requête externe.

Par documentation :

Si une requête contient des appels de fonction d'agrégation, mais pas de GROUP BY clause, le regroupement se produit toujours :le résultat est une ligne de groupe unique (ou peut-être aucune ligne du tout, si la ligne unique est ensuite éliminée par HAVING ). Il en va de même s'il contient un HAVING clause, même sans aucun appel de fonction d'agrégat ou GROUP BY clause.

Il est à noter que l'ajout d'un GROUP BY clause avec seulement une expression constante (qui est autrement complètement inutile !) fonctionne aussi. Voir l'exemple ci-dessous. Mais je préfère ne pas utiliser cette astuce, même si elle est courte, bon marché et simple, car ce qu'elle fait n'est pas évident.

La requête suivante ne nécessite qu'une analyse de table unique et renvoie les 7 premières catégories classées par nombre. Si (et seulement si ) il y a plus de catégories, le reste est résumé dans "Autres" :

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
  • Vous devez briser les égalités si plusieurs catégories peuvent avoir le même nombre dans le 7e/8e rang. Dans mon exemple, les catégories avec le plus petit categoryid gagner une telle course.

  • Les parenthèses sont obligatoires pour inclure un LIMIT ou ORDER BY clause à une branche individuelle d'un UNION requête.

  • Il vous suffit de vous joindre à la table category pour les 7 premières catégories. Et il est généralement moins cher d'agréger d'abord et de rejoindre plus tard dans ce scénario. Donc, ne rejoignez pas la requête de base dans le CTE (expression de table commune) nommé cte , joignez uniquement le premier SELECT de l'UNION requête, c'est moins cher.

  • Vous ne savez pas pourquoi vous avez besoin de COALESCE . Si vous avez une clé étrangère en place à partir de contents.categoryid à category.id et les deux contents.categoryid et category.name sont définis NOT NULL (comme ils devraient probablement l'être), alors vous n'en avez pas besoin.

L'impair GROUP BY true

Cela fonctionnerait aussi :

...

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

Et j'obtiens même des plans de requête légèrement plus rapides. Mais c'est un hack plutôt bizarre...

Violon SQL démontrant tout.

Réponse connexe avec plus d'explications pour le UNION ALL / LIMIT techniques :

  • Sommez les résultats de quelques requêtes, puis trouvez le top 5 en SQL