エクゼキュータ。集約(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の関数ポインタで式が評価されるためロジックが一様ではない。