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 parHAVING
). Il en va de même s'il contient unHAVING
clause, même sans aucun appel de fonction d'agrégat ouGROUP 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
ouORDER BY
clause à une branche individuelle d'unUNION
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 premierSELECT
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 decontents.categoryid
àcategory.id
et les deuxcontents.categoryid
etcategory.name
sont définisNOT 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