Corrigez le LEFT JOIN
Cela devrait fonctionner :
SELECT o.name AS organisation_name, count(e.id) AS total_used
FROM organisations o
LEFT JOIN exam_items e ON e.organisation_id = o.id
AND e.item_template_id = #{sanitize(item_template_id)}
AND e.used
GROUP BY o.name
ORDER BY o.name;
Vous aviez une LEFT [OUTER] JOIN
mais le dernier WHERE
les conditions l'ont fait agir comme un simple [INNER] JOIN
.
Déplacez la ou les conditions vers le JOIN
clause pour que cela fonctionne comme prévu. De cette façon, seules les lignes qui remplissent toutes ces conditions sont jointes en premier lieu (ou les colonnes de la droite table sont remplies avec NULL). Comme vous l'aviez, les lignes jointes sont testées pour des conditions supplémentaires pratiquement après le LEFT JOIN
et supprimés s'ils ne passent pas, comme avec un simple JOIN
.
count()
ne renvoie jamais NULL pour commencer. C'est une exception parmi les fonctions d'agrégation à cet égard. Par conséquent, jamais logique, même avec des paramètres supplémentaires. Le manuel :COALESCE(COUNT(col))
Il convient de noter que sauf pour
count
, ces fonctions renvoient une valeur nulle lorsqu'aucune ligne n'est sélectionnée.
Bold emphase mienne. Voir :
- Compter le nombre d'attributs NULL pour une ligne
count()
doit être sur une colonne définie NOT NULL
(comme e.id
), ou où la condition de jointure garantit NOT NULL
(e.organisation_id
, e.item_template_id
, ou e.used
) dans l'exemple.
Depuis used
est de type boolean
, l'expression e.used = true
est le bruit qui brûle jusqu'à e.used
.
Depuis o.name
n'est pas défini UNIQUE NOT NULL
, vous voudrez peut-être GROUP BY o.id
à la place (id
étant le PK) - à moins que vous n'ayez l'intention pour replier les lignes portant le même nom (y compris NULL).
Agrégez d'abord, rejoignez plus tard
Si la plupart ou toutes les lignes de exam_items
sont comptés dans le processus, cette requête équivalente est généralement considérablement plus rapide/moins chère :
SELECT o.id, o.name AS organisation_name, e.total_used
FROM organisations o
LEFT JOIN (
SELECT organisation_id AS id -- alias to simplify join syntax
, count(*) AS total_used -- count(*) = fastest to count all
FROM exam_items
WHERE item_template_id = #{sanitize(item_template_id)}
AND used
GROUP BY 1
) e USING (id)
ORDER BY o.name, o.id;
(Cela suppose que vous ne voulez pas replier les lignes portant le même nom comme mentionné ci-dessus - le cas typique.)
Maintenant, nous pouvons utiliser le count(*)
plus rapide / plus simple dans la sous-requête, et nous n'avons pas besoin de GROUP BY
dans le SELECT
externe .
Voir :
- Plusieurs appels array_agg() dans une seule requête