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

Requête avec LEFT JOIN ne renvoyant pas de lignes pour un nombre de 0

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, COALESCE(COUNT(col)) jamais logique, même avec des paramètres supplémentaires. Le manuel :

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