web-dev-qa-db-ja.com

GROUP BYとHAVINGを使用しているときに関数を2回呼び出すのを避ける方法は?

親子関係のテーブルを含む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関数が複数回呼び出されるのではないかと思います。

pdate:クエリプランは次のとおりです。

                                                 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の複数の呼び出しを最適化しますか?

そうでない場合、より効率的なこのクエリの代替形式はありますか?

4
vallismortis

2番目のクエリ(HAVING句を使用して実装するクエリ)はおそらく高速です。最初のクエリ(副選択あり)では、postgresはテーブル全体のカウント値を計算する必要があります。 2番目のクエリでは、1を超えるカウント値に達すると、行を無視してカウントを開始できます(ただし、postgresがそれを実行するのに十分スマートであるかどうかは100%わかりませんが、確かにそうです)。

COUNT()は集計関数であるため、返される行数に関係なく、実行される回数だけ実行されます。集約関数ではない関数がある場合は、サブセレクトでグループとwhere/having句を実行する方が高速です。

私が言及しているものの例:

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;

具体的にあなたの質問に答えるために-はい、postgresは計算した集計値を追跡し、HAVING句内で(再計算するのではなく)再利用します。私はそれがSELECT節でもそれらを再利用すると信じています(奇妙な理由でSELECTでまったく同じ集計を複数回実行した場合)

引用するには Postgresの優れたドキュメント (太字)

集計とSQLのWHEREおよびHAVING句の間の相互作用を理解することが重要です。 WHEREとHAVINGの基本的な違いは次のとおりです:WHEREはグループと集計が計算される前に入力行を選択します(したがって、集計計算に入る行を制御します)、HAVINGはグループの後にグループ行を選択しますしたがって、WHERE句に集計関数を含めることはできません。集計を使用して、集計への入力となる行を決定することは意味がありません。一方、HAVING句には常に集計関数が含まれます。 (厳密に言えば、集計を使用しないHAVING句を記述できますが、ほとんど役に立ちません。同じ条件をWHEREステージでより効率的に使用できます。)

これは、計算された値を再利用することを具体的に言っているわけではありませんが、集計の計算後にHAVING句が使用されていることを示しています。

5
Joishi Bodio