Requête: comptez plusieurs agrégats par article
-
07-07-2019 - |
Question
Il est souvent nécessaire d'afficher une liste d'éléments de base de données et un certain nombre de chiffres globaux pour chaque élément. Par exemple, lorsque vous tapez le texte du titre sur Stack Overflow, la liste des questions connexes apparaît. La liste affiche les titres des entrées liées et le nombre unique de réponses pour chaque titre.
J'ai un problème similaire mais j'ai besoin de plusieurs agrégats. Je souhaite afficher une liste d'éléments dans l'un des 3 formats en fonction des options de l'utilisateur:
- Nom de mon élément (15 au total, 13 dont je suis propriétaire)
- Nom de mon élément (15 au total)
- Nom de mon élément (13 dont je suis propriétaire)
Ma base de données est:
- éléments : itemId, itemName, ownerId
- catégories : catId, catName
- carte : mapId, itemId, catId
La requête ci-dessous obtient: nom de la catégorie, nombre d'identifiants d'élément par catégorie
SELECT
categories.catName,
COUNT(map.itemId) AS item_count
FROM categories
LEFT JOIN map
ON categories.catId = map.catId
GROUP BY categories.catName
Celui-ci obtient: nom de la catégorie, nombre d'identifiants d'élément par catégorie pour cet identifiant de propriétaire uniquement
SELECT categories.catName,
COUNT(map.itemId) AS owner_item_count
FROM categories
LEFT JOIN map
ON categories.catId = map.catId
LEFT JOIN items
ON items.itemId = map.itemId
WHERE owner = @ownerId
GROUP BY categories.catId
Mais comment puis-je les obtenir en même temps dans une seule requête? I.e .: nom de la catégorie, nombre d'identifiants d'élément par catégorie, nombre d'identifiants d'élément par catégorie pour ce propriétaire uniquement id
Bonus. Comment puis-je éventuellement ne récupérer que le nombre de catId! = 0 pour aucun de ceux-ci? En essayant & «WHERE item_count & Lt; & Gt; 0 & Quot; Je reçois:
MySQL said: Documentation
#1054 - Unknown column 'rid_count' in 'where clause'
La solution
Voici une astuce: calculer un SUM()
des valeurs connues pour être 1 ou 0 équivaut à un COUNT()
des lignes où la valeur est 1. Et vous savez qu'une comparaison booléenne renvoie 1 ou 0 (ou NULL).
SELECT c.catname, COUNT(m.catid) AS item_count,
SUM(i.ownerid = @ownerid) AS owner_item_count
FROM categories c
LEFT JOIN map m USING (catid)
LEFT JOIN items i USING (itemid)
GROUP BY c.catid;
En ce qui concerne la question bonus, vous pouvez simplement faire une jointure interne au lieu d'une jointure externe, ce qui signifie que seules les catégories comportant au moins une ligne dans map
seront renvoyées.
SELECT c.catname, COUNT(m.catid) AS item_count,
SUM(i.ownerid = @ownerid) AS owner_item_count
FROM categories c
INNER JOIN map m USING (catid)
INNER JOIN items i USING (itemid)
GROUP BY c.catid;
Voici une autre solution moins efficace mais que je vais vous montrer pour expliquer pourquoi vous avez eu l'erreur:
SELECT c.catname, COUNT(m.catid) AS item_count,
SUM(i.ownerid = @ownerid) AS owner_item_count
FROM categories c
LEFT JOIN map m USING (catid)
LEFT JOIN items i USING (itemid)
GROUP BY c.catid
HAVING item_count > 0;
Vous ne pouvez pas utiliser d'alias de colonne dans la clause WHERE
car les expressions de la clause GROUP BY
sont évaluées avant les expressions de la liste de sélection. En d'autres termes, les valeurs associées aux expressions de liste de sélection ne sont pas encore disponibles.
Vous pouvez utiliser des alias de colonne dans les clauses HAVING
, ORDER BY
et <=>. Ces clauses sont exécutées une fois que toutes les expressions de la liste de sélection ont été évaluées.
Autres conseils
Vous pouvez insérer une instruction CASE dans votre SUM ():
SELECT categories.catName,
COUNT(map.itemId) AS item_count,
SUM(CASE WHEN owner= @ownerid THEN 1 ELSE 0 END) AS owner_item_count
FROM categories
LEFT JOIN map ON categories.catId = map.catId
LEFT JOIN items ON items.itemId = map.itemId
GROUP BY categories.catId
HAVING COUNT(map.itemId) > 0
SELECT categories.catName,
COUNT(map.itemId) AS item_count,
COUNT(items.itemId) AS owner_item_count
FROM categories
INNER JOIN map
ON categories.catId = map.catId
LEFT JOIN items
ON items.itemId = map.itemId
AND items.owner = @ownerId
GROUP BY categories.catId
Notez que vous pouvez utiliser une clause HAVING
sur owner_item_count
, mais la jointure interne prend en charge item_count pour vous.