在 ANY() 条件中使用来自子查询的数组表达式

Mar*_*tin 3 postgresql syntax subquery

我有一个相对复杂的查询,其子查询获取一个数组,如下所示:

...
ARRAY(SELECT category_id FROM category_schedule_con con
      WHERE s.id = con.schedule_id ORDER BY category_id) AS cats,
...
Run Code Online (Sandbox Code Playgroud)

并希望在以后的 WHERE 条件中使用数组“cats”,例如

...
WHERE 4 = ANY(cats)
...
Run Code Online (Sandbox Code Playgroud)

但这不起作用,因为它指出“cats”列不存在。c/p'ing 子查询到ANY子句中会产生预期的结果。

Erw*_*ter 6

解释

根据SQL 标准(Postgres 实现的)中的定义,您可以在ORDER BYor 中引用输出列GROUP BY,但不能WHEREorHAVING子句中引用。手册

输出列的名称可用于在ORDER BYandGROUP BY子句中引用列的值,但不能在WHEREorHAVING子句中引用 ;在那里你必须写出表达式。

有关的:

显然,您的子查询是列表中相关子查询表达式(由于过度简化而隐藏在问题中)。SELECT

为了避免在WHERE子句中重复冗长/昂贵的表达式,您可以在FROM列表中使用子查询。SELECT列表中的相关子查询不能被子WHERE句中的别名引用,它只是一个像任何其他列一样的输出列。

很有可能,您的查询效率会更高......

更好的查询

应用上述内容,此查询将起作用:

SELECT s.*, con.cats
FROM   some_table s  -- guessing the missing query
JOIN  (
   SELECT schedule_id
        , array_agg(category_id ORDER BY category_id) AS cats
   FROM   category_schedule_con
   GROUP  BY 1
   ) con ON con.schedule_id = s.id
WHERE  3 = ANY(cats);
Run Code Online (Sandbox Code Playgroud)

现在您可以catsWHERE子句中引用列别名。但是由于多种原因,这个查询对于大表来说效率非常低。最重要的是,谓词不是sargable

使用GROUP BY聚集函数array_agg(),而不是ARRAY构造函数,因为我们是生产在一个单一的查询数组。

ORDER BY几乎可以将子句应用于任何聚合函数,但排序的子查询通常性能更好:

SELECT s.*, con.cats
FROM   some_table s
JOIN  (
   SELECT id, array_agg(category_id) AS cats
   FROM   (
      SELECT schedule_id AS id, category_id  -- alias id for convenience
      FROM   category_schedule_con
      ORDER  BY 1, 2  -- to get ordered list per schedule_id
      ) con
   GROUP  BY 1
   ) con USING (id)
WHERE  3 = ANY(cats);
Run Code Online (Sandbox Code Playgroud)

更重要的是,将谓词(WHERE条件)下拉到子查询中,以便可以使用索引提前排除不相关的行。大表要快得多:

SELECT s.*, con.cats
FROM   some_table s
JOIN  (
   SELECT id, array_agg(category_id) AS cats
   FROM  (
      SELECT schedule_id AS id, category_id
      FROM   category_schedule_con c
      WHERE  EXISTS (
         SELECT 1 FROM category_schedule_con
         WHERE  schedule_id = c.schedule_id
         AND    category_id = 3
         )
      ORDER  BY 1, 2
      ) con
   GROUP BY 1
   ) con USING (id);
Run Code Online (Sandbox Code Playgroud)

根据数据分布,LATERAL连接(需要 Postgres 9.3+)可能更有效:

SELECT s.*, con.cats
FROM   some_table s
     , LATERAL (
   SELECT ARRAY (
      SELECT category_id
      FROM   category_schedule_con
      WHERE  schedule_id = s.id
      ORDER  BY 1
      ) AS cats
   ) con
WHERE  EXISTS (
   SELECT 1 FROM category_schedule_con
   WHERE  schedule_id = s.id
   AND    category_id = 3
   );
Run Code Online (Sandbox Code Playgroud)

关于LATERAL

但它应该是最快的,以反转的逻辑:通过测距启动schedule_idcategory_id = 3,自联接category_schedule_con和聚集加入到其他表之前:

SELECT s.*, con.cats
FROM  (
   SELECT id, array_agg(c.category_id) AS cats
   FROM  (
      SELECT schedule_id AS id
      FROM   category_schedule_con
      WHERE  category_id = 3
      ) x
   JOIN   category_schedule_con c USING (id)
   GROUP  BY id
   ) con
JOIN  some_table s USING (id);
Run Code Online (Sandbox Code Playgroud)

指数

确保有一个多列索引,如:

CREATE INDEX category_schedule_con_foo_idx ON category_schedule_con
(schedule_id, category_id);
Run Code Online (Sandbox Code Playgroud)

对于最后一个查询,我们需要列的倒序

CREATE INDEX category_schedule_con_bar_idx ON category_schedule_con
(category_id, schedule_id);
Run Code Online (Sandbox Code Playgroud)

另一个有两列的再次切换回来。在只有两个指标(category_id),并(schedule_id)就工作速度快,太;

两列上的 PK 或 UNIQUE 约束也适用。