エクゼキュータ。集約(Aggregate)

適当なクエリを流して、GROUP BYによる集約がどう実行されるのか見てみた。

DDLとデータ
create table person (
  name text primary key,
  country text not null,
  age integer not null
);

insert into person values
('aaa', 'jp', 20),
('bbb', 'us', 30),
('ccc', 'jp', 30),
('ddd', 'us', 20),
('eee', 'us', 20);
クエリ

国別の集計で人の年齢の合計が50より大きくなる国。

select 
  country, 
  sum(age) as age 
from 
  person 
group by 
  country 
having 
  sum(age) > 50;
実行計画
 HashAggregate  (cost=24.52..28.02 rows=200 width=36)
   Filter: (sum(age) > 50)
   ->  Seq Scan on person  (cost=0.00..18.30 rows=830 width=36)
プランツリー

昨日の設定で出力。

DEBUG:  plan:
DETAIL:     {PLANNEDSTMT
           :commandType 1
           :canSetTag true
           :planTree
              {AGG
              :startup_cost 24.52
              :total_cost 28.02
              :plan_rows 200
              :plan_width 36
              :targetlist (
                 {TARGETENTRY
                 :expr
                    {VAR
                    :varno 65001
                    :varattno 2
                    :vartype 25
                    :vartypmod -1
                    :varlevelsup 0
                    :varnoold 1
                    :varoattno 2
                    }
                 :resno 1
                 :resname country
                 :ressortgroupref 1
                 :resorigtbl 32944
                 :resorigcol 2
                 :resjunk false
                 }
                 {TARGETENTRY
                 :expr
                    {AGGREF
                    :aggfnoid 2108
                    :aggtype 20
                    :args (
                       {VAR
                       :varno 65001
                       :varattno 3
                       :vartype 23
                       :vartypmod -1
                       :varlevelsup 0
                       :varnoold 1
                       :varoattno 3
                       }
                    )
                    :agglevelsup 0
                    :aggstar false
                    :aggdistinct false
                    }
                 :resno 2
                 :resname age
                 :ressortgroupref 0
                 :resorigtbl 0
                 :resorigcol 0
                 :resjunk false
                 }
              )
              :qual (
                 {OPEXPR
                 :opno 419
                 :opfuncid 477
                 :opresulttype 16
                 :opretset false
                 :args (
                    {AGGREF
                    :aggfnoid 2108
                    :aggtype 20
                    :args (
                       {VAR
                       :varno 65001
                       :varattno 3
                       :vartype 23
                       :vartypmod -1
                       :varlevelsup 0
                       :varnoold 1
                       :varoattno 3
                       }
                    )
                    :agglevelsup 0
                    :aggstar false
                    :aggdistinct false
                    }
                    {CONST
                    :consttype 23
                    :consttypmod -1
                    :constlen 4
                    :constbyval true
                    :constisnull false
                    :constvalue 4 [ 50 0 0 0 ]
                    }
                 )
                 }
              )
              :lefttree
                 {SEQSCAN
                 :startup_cost 0.00
                 :total_cost 18.30
                 :plan_rows 830
                 :plan_width 36
                 :targetlist (
                    {TARGETENTRY
                    :expr
                       {VAR
                       :varno 1
                       :varattno 1
                       :vartype 25
                       :vartypmod -1
                       :varlevelsup 0
                       :varnoold 1
                       :varoattno 1
                       }
                    :resno 1
                    :resname <>
                    :ressortgroupref 0
                    :resorigtbl 0
                    :resorigcol 0
                    :resjunk false
                    }
                    {TARGETENTRY
                    :expr
                       {VAR
                       :varno 1
                       :varattno 2
                       :vartype 25
                       :vartypmod -1
                       :varlevelsup 0
                       :varnoold 1
                       :varoattno 2
                       }
                    :resno 2
                    :resname <>
                    :ressortgroupref 0
                    :resorigtbl 0
                    :resorigcol 0
                    :resjunk false
                    }
                    {TARGETENTRY
                    :expr
                       {VAR
                       :varno 1
                       :varattno 3
                       :vartype 23
                       :vartypmod -1
                       :varlevelsup 0
                       :varnoold 1
                       :varoattno 3
                       }
                    :resno 3
                    :resname <>
                    :ressortgroupref 0
                    :resorigtbl 0
                    :resorigcol 0
                    :resjunk false
                    }
                 )
                 :qual <>
                 :lefttree <>
                 :righttree <>
                 :initPlan <>
                 :extParam (b)
                 :allParam (b)
                 :scanrelid 1
                 }
              :righttree <>
              :initPlan <>
              :extParam (b)
              :allParam (b)
              :aggstrategy 2
              :numCols 1
              :grpColIdx 2
              :grpOperators 98
              :numGroups 200
              }
           :rtable (
              {RTE
              :alias <>
              :eref
                 {ALIAS
                 :aliasname person
                 :colnames ("name" "country" "age")
                 }
              :rtekind 0
              :relid 32944
              :inh false
              :inFromCl true
              :requiredPerms 2
              :checkAsUser 0
              }
           )
           :resultRelations <>
           :utilityStmt <>
           :intoClause <>
           :subplans <>
           :rewindPlanIDs (b)
           :returningLists <>
           :rowMarks <>
           :relationOids (o 32944)
           :nParamExec 0
           }
  • GROUP BYはAGGで、集約関数はAGGREFで表されている。
  • HAVINGはAGGのqualにあるOPEXPRで表されている。
  • personテーブルへの順スキャンのノードはAGGのlefttreeで。
  • AGGのrighttreeは空。
  • AGGのaggstrategy=2は集約にハッシュテーブルを使うことを表す。ほかにはソートを使う手法があるよう。


集約はExecAgg()で行われる。

TupleTableSlot *
ExecAgg(AggState *node)

だいたいこんな感じの処理が行われる。

  • lefttreeのノードを実行し、返されるタプルをグループごとにハッシュテーブルに格納する。
  • 集約関数の計算をする。
  • 全件をハッシュテーブルに入れ終えたら、ハッシュテーブルのエントリを順に見て(HAVINGの)条件に満たすものを探す。
    • あれば呼び出し元に返す、なければ次のエントリを探す。
  • 次に実行されたら、ハッシュテーブルのまだチェックしていないエントリを順に見ていく。
  • すべてのエントリをチェックしたら終了。
  • 条件を満たすかどうかのチェックは、ExecQual()で行われる。ExprStateの関数ポインタで式が評価されるためロジックが一様ではない。