43

我正在尝试获取以下内容以返回在 PostgreSQL 中使用左连接的每个组织的计数,但我无法弄清楚它为什么不起作用:

  select o.name as organisation_name,
         coalesce(COUNT(exam_items.id)) as total_used
  from organisations o
  left join exam_items e on o.id = e.organisation_id
  where e.item_template_id = #{sanitize(item_template_id)}
  and e.used = true
  group by o.name
  order by o.name

使用coalesce似乎不起作用。我无计可施!任何帮助都将不胜感激!

为了澄清什么不起作用,目前查询仅返回计数大于 0 的组织的值。我希望它为每个组织返回一行,无论计数如何。

表定义:

TABLE exam_items
  id serial NOT NULL
  exam_id integer
  item_version_id integer
  used boolean DEFAULT false
  question_identifier character varying(255)
  organisation_id integer
  created_at timestamp without time zone NOT NULL
  updated_at timestamp without time zone NOT NULL
  item_template_id integer
  stem_id integer
  CONSTRAINT exam_items_pkey PRIMARY KEY (id)

TABLE organisations
  id serial NOT NULL
  slug character varying(255)
  name character varying(255)
  code character varying(255)
  address text
  organisation_type integer
  created_at timestamp without time zone NOT NULL
  updated_at timestamp without time zone NOT NULL
  super boolean DEFAULT false
  CONSTRAINT organisations_pkey PRIMARY KEY (id)
4

2 回答 2

82

修复LEFT JOIN

这应该有效:

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;

你有 aLEFT [OUTER] JOIN但后来的WHERE条件使它表现得像一个 plain [INNER] JOIN
将条件移至JOIN子句以使其按预期工作。这样,只有满足所有这些条件的行首先被连接(或者表中的列被填充为 NULL)。就像您所拥有的一样,连接的行实际上会在之后测试附加条件,如果它们不通过则将其LEFT JOIN删除,就像使用普通JOIN.

count()从不返回 NULL 开头。在这方面,聚合函数是一个例外。因此,即使有附加参数,也永远不会有意义。手册:COALESCE(COUNT(col))

需要注意的是,除了 之外count,这些函数在没有选择行时返回空值。

大胆强调我的。看:

count()必须位于已定义的列上NOT NULL(如e.id),或示例中连接条件保证的列NOT NULLe.organisation_ide.item_template_ide.used)。

既然used是类型boolean,表现e.used = true就是燃烧到刚刚的噪音e.used

由于o.name未定义UNIQUE NOT NULL,您可能希望GROUP BY o.id改为(id作为 PK) - 除非您打算折叠具有相同名称的行(包括 NULL)。

先聚合,后加入

如果在该过程中计算了大部分或所有行,exam_items则此等效查询通常会更快/更便宜:

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;

(这是假设您不想像上面提到的那样折叠具有相同名称的行 - 典型情况。)

现在我们可以count(*)在子查询中使用更快/更简单的,我们不需要GROUP BY在外部SELECT

看:

于 2013-03-18T00:30:49.223 回答
13

为了清楚起见,

重要的是GROUP BY MAIN_TABLE,它将处理来自的 NULL 值SOME_TABLE

SELECT COUNT(ST.ID)
FROM MAIN_TABLE MT
LEFT JOIN SOME_TABLE ST ON MT.ID = ST.MT_ID

GROUP BY MT.ID -- this line is a must
于 2015-10-08T16:26:11.567 回答