使用 GROUP BY 和 HAVING 时如何避免调用函数两次?

val*_*tis 4 postgresql performance group-by postgresql-9.2 query-performance

我有一个带有父子关系表的 PostgreSQL 数据库 (9.2)。我有一个查询,用于查找具有多个父节点的节点。

以下查询有效并返回正确的结果:

SELECT node,parents FROM
(
  SELECT nr.child AS node, COUNT(nr.parent) AS parents 
  FROM node_relation nr 
  GROUP BY nr.child
) AS count WHERE parents > 1;
Run Code Online (Sandbox Code Playgroud)

结果集:

 node   | parents
--------+---------
 n21174 |       2
 n8635  |       2
(2 rows)
Run Code Online (Sandbox Code Playgroud)

表定义为:

            Table "public.node_relation"
   Column    |         Type          |   Modifiers
-------------+-----------------------+---------------
 child       | character varying(50) | not null
 parent      | character varying(50) | not null
Indexes:
    "node_relation_pkey" PRIMARY KEY, btree (child, parent)
Run Code Online (Sandbox Code Playgroud)

我重新编写了查询以不使用子选择:

SELECT child AS node, COUNT(parent) AS parents 
FROM node_relation 
GROUP BY child 
HAVING COUNT(parent) > 1;
Run Code Online (Sandbox Code Playgroud)

新查询有效,但我想知道 COUNT 函数被多次调用。

更新:这是查询计划:

                                                 QUERY PLAN
-------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=0.00..1658.81 rows=19970 width=16)
   Filter: (count(parent) > 1)
   ->  Index Only Scan using node_relation_pkey on node_relation  (cost=0.00..1259.40 rows=19971 width=16)
Run Code Online (Sandbox Code Playgroud)

我更愿意使用parents别名,但以下方法不起作用:

SELECT child AS node, COUNT(parent) AS parents 
FROM node_relation 
GROUP BY child 
HAVING parents > 1;

ERROR:  column "parents" does not exist
LINE 1: ...parents FROM node_relation GROUP BY child HAVING parents > ...
                                                            ^
Run Code Online (Sandbox Code Playgroud)

PostgreSQL 会优化对 的多次调用COUNT吗?

如果没有,是否有此查询的替代形式会更有效?

Joi*_*dio 5

你的第二个查询(你用HAVING子句实现它的那个)可能更快。在您的第一个查询(使用子选择)中,postgres 必须计算整个表的计数值。在您的第二个查询中,一旦计数值达到 1 以上,它就可以开始忽略要计数的行(尽管我不是 100% 知道 postgres 是否足够聪明来做到这一点 - 不过我很确定它是)。

由于COUNT()是一个聚合函数,无论返回的行数如何,它都会运行多少次。如果您有一个不是聚合函数的函数,那么在子选择中运行您的组和 where/have 子句可能会更快。

我所指的示例:

SELECT
  some_non_agg_function(a.id, a.child)
FROM join_tab1 a
GROUP BY a.id, a.child
HAVING COUNT(a.id) > 1;
-- probably not as fast as
WITH rows_to_process AS (
  SELECT DISTINCT
    id, child
  FROM join_tab1 a
  GROUP BY a.id, a.child
  HAVING COUNT(a.id) > 1
) SELECT
  some_non_agg_function(id, child)
FROM rows_to_process;
Run Code Online (Sandbox Code Playgroud)

要具体回答您的问题 - 是的,postgres 将跟踪它计算的聚合值并在HAVING子句中重新使用它们(而不是重新计算它们)。我相信它也会在SELECT子句中重新使用它们(如果出于某种奇怪的原因你在 中多次运行完全相同的聚合SELECT

引用Postgres 的优秀文档(我的粗体)

了解聚合与 SQL 的 WHERE 和 HAVING 子句之间的交互非常重要。WHERE 和 HAVING 之间的根本区别在于:WHERE 在计算组和聚合之前选择输入行(因此,它控制哪些行进入聚合计算),而 HAVING 在计算组和聚合之后选择组行。因此,WHERE 子句不得包含聚合函数;尝试使用聚合来确定哪些行将作为聚合的输入是没有意义的。另一方面,HAVING 子句总是包含聚合函数。(严格来说,您可以编写不使用聚合的 HAVING 子句,但它很少有用。在 WHERE 阶段可以更有效地使用相同的条件。)

这并没有具体说它重用计算的值..但它暗示它说HAVING在计算聚合后使用该子句。

  • 如果您对查询运行解释,您可以看到它 - 您将看到 postgres 只运行一次 COUNT() 聚合。:) (2认同)