Postgresql

使用 GROUP BY 和 HAVING 時如何避免兩次呼叫函式?

  • October 29, 2015

我有一個帶有父子關係表的 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;

結果集:

node   | parents
--------+---------
n21174 |       2
n8635  |       2
(2 rows)

表定義為:

           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)

我重寫了查詢以不使用子選擇:

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

新查詢有效,但我想知道 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)

我更喜歡使用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 > ...
                                                           ^

PostgreSQL 會優化 ? 的多次呼叫COUNT

如果沒有,是否有這種查詢的替代形式會更有效?

您的第二個查詢(您使用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;

HAVING要具體回答您的問題 - 是的,postgres 將跟踪它計算的聚合值並在子句中重新使用它們(而不是重新計算它們) 。我相信它也會在SELECT子句中重新使用它們(如果出於某種奇怪的原因,您在 中多次執行完全相同的聚合SELECT

引用Postgres 的優秀文件(我的粗體)

了解聚合與 SQL 的 WHERE 和 HAVING 子句之間的互動非常重要。**WHERE 和 HAVING 之間的根本區別在於:WHERE 在計算組和聚合之前選擇輸入行(因此,它控制哪些行進入聚合計算),而 HAVING 在計算組和聚合之後選擇組行。**因此,WHERE 子句不能包含聚合函式;嘗試使用聚合來確定哪些行將作為聚合的輸入是沒有意義的。另一方面,HAVING 子句總是包含聚合函式。(嚴格來說,您可以編寫不使用聚合的 HAVING 子句,但它很少有用。在 WHERE 階段可以更有效地使用相同的條件。)

這並沒有具體說它重新使用計算值.. 但它暗示它說該HAVING子句是在計算聚合之後使用的。

引用自:https://dba.stackexchange.com/questions/119575