Corrigir o LEFT JOIN
Isso deve funcionar:
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;
Você tinha um
LEFT [OUTER] JOIN
mas o posterior WHERE
condições fizeram com que ele agisse como um simples [INNER] JOIN
.Mova a(s) condição(ões) para o
JOIN
cláusula para fazê-lo funcionar como pretendido. Dessa forma, apenas as linhas que atendem a todas essas condições são unidas em primeiro lugar (ou colunas da direita tabela são preenchidos com NULL). Como você fez, as linhas unidas são testadas para condições adicionais virtualmente depois o LEFT JOIN
e removidos se não passarem, assim como com um simples JOIN
. count()
nunca retorna NULL para começar. É uma exceção entre as funções agregadas a esse respeito. Portanto, COALESCE(COUNT(col))
Deve-se observar que exceto paracount
, essas funções retornam um valor nulo quando nenhuma linha é selecionada.
Minha ênfase em negrito. Ver:
- Conte o número de atributos que são NULL para uma linha
count()
deve estar em uma coluna definida NOT NULL
(como e.id
), ou onde a condição de junção garante NOT NULL
(e.organisation_id
, e.item_template_id
, ou e.used
) no exemplo. Desde
used
é tipo boolean
, a expressão e.used = true
é o ruído que se reduz a apenas e.used
. Desde
o.name
não está definido UNIQUE NOT NULL
, você pode querer GROUP BY o.id
em vez disso (id
sendo o PK) - a menos que você intenha para dobrar linhas com o mesmo nome (incluindo NULL). Agregue primeiro, junte-se depois
Se a maioria ou todas as linhas de
exam_items
são contados no processo, essa consulta equivalente é normalmente consideravelmente mais rápida / mais barata: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;
(Isto supõe que você não deseja dobrar linhas com o mesmo nome mencionado acima - o caso típico.)
Agora podemos usar o
count(*)
mais rápido/simples na subconsulta e não precisamos de GROUP BY
no SELECT
externo . Ver:
- Várias chamadas array_agg() em uma única consulta