概要
本記事では、SQLの論理的な実行順序の説明を前提とした上で、PostgreSQLでGROUP BYにエイリアスが指定できる理由を内部の解析から見ていきます。
まとめ
SQLでよく言われる
FROM → WHERE → GROUP BY → SELECT → ORDER BY → LIMIT
は論理的な実行順序であり、内部実装そのものではありません。
PostgreSQLでは、クエリの実行前に
構文解析(Parsing)・意味解析(Semantic Analysis)が行われ、
その段階で SELECT 句の TargetEntry(番号)が確定します。
GROUP BY 句は、実行時に列名やエイリアスを解釈しているのではなく、
構文解析・意味解析フェーズで確定した TargetEntry を参照して解決されます。
そのため
GROUP BY category と GROUP BY cg(category のエイリアス) は
同じ TargetEntry を指し、同一扱いになります。
この挙動は debug_print_parse を用いたクエリツリーのログから、
ressortgroupref と tleSortGroupRef が一致していることとして確認できます。
以下のスライドでも簡単にまとめていますのでご覧ください!
背景
上司に以下の記事をおすすめされたので理解したいと思います。
ここで言われている実行順序とはなんだろうなと思っていて、以下の本で実行順序について書かれていたので紹介します。
SELECT文の内部的な実行順序
FROM -> WHERE -> GROUP BY -> SELCT -> ORDER BY
DBMSによって異なるということでしたが、大まかなイメージはこれだということでした。
この論理的な実行順序の説明だけを見ると、
以下のようなクエリは直感的には避けた方がよさそうに見えます。
SELECT category AS cg,COUNT(*)
FROM hobbies
GROUP BY cg;
GROUP BY句はSELCT句より先に実行されるので、cgという別名をDBMSが知らないためです。
しかし、MySQLとPostgreSQLはこの順序でも問題なく実行できます。
PostgreSQLではGROUP BY句でASを使用できる
PostgreSQLだと以下のようになります。
確かにこの記述で問題なく実行できることがわかりました。
PostgreSQLのドキュメントを見ると、ASも使用可能なことがわかりました。
出力列(SELECTリスト項目)の名前
これがASだと思います。
GROUP BY will condense into a single row all selected rows that share the same values for the grouped expressions. An expression used inside a grouping_element can be an input column name, or the name or ordinal number of an output column (SELECT list item), or an arbitrary expression formed from input-column values. In case of ambiguity, a GROUP BY name will be interpreted as an input-column name rather than an output column name.
GROUP BYは、グループ化のために与えられた式を評価し、結果が同じ値になった行を1つの行にまとめる機能を持ちます。 grouping_elementの内側で使われるexpressionには、入力列の名前、出力列(SELECTリスト項目)の名前/序数、あるいは入力列の値から計算される任意の式を取ることができます。 判断がつかない時は、GROUP BYの名前は出力列名ではなく入力列名として解釈されます。
ドキュメント
In strict SQL, GROUP BY can only group by columns of the source table but PostgreSQL extends this to also allow GROUP BY to group by columns in the select list. Grouping by value expressions instead of simple column names is also allowed.
厳密なSQLでは、GROUP BYはソーステーブルの列でのみグループ化できますが、PostgreSQLではこれを拡張し、SELECT句内の列によるグループ化も許可しています。単純な列名ではなく値式によるグループ化も許可されています。
ドキュメント
こちらでもGROUP BY句ではASの使用が可能だとわかります。
PostgreSQLのSELECT文の内部的な実行順序
- WITH内のSELECT
- FROM
- WHERE
- GROUP BY
- HAVING
- SELECT
- 列に対する関数やDISTINCTなど
- UNION、INTERSECT、EXCEPT
- ORDER BY
- LIMIT
上のような実行順序になってはいますが、前の章で説明したようにGROUP BY句でASが使えるということがわかりました。
つまり、論理的にはGROUP BY句はSELECT句より先に実行されますが、PostgreSQLはGROUP BY句にASを使用できるということです。
論理的な実行順序が逆転したのか?
という疑問が出てきましたが、結論として論理的な実行順序が逆転したわけではありません。
PostgreSQLでは、クエリが実行される前に
構文解析(Parsing)と意味解析(Semantic Analysis)が行われ、
その段階でSELECT句の列やエイリアスが解決されます。
そのため、GROUP BY 句は実行時に SELECT 句の結果を参照しているのではなく、
実行前に確定した解析結果(クエリツリー)をもとに処理されているだけです。
この挙動を理解するために、次にPostgreSQLのクエリの構文解析と意味解析について調査します。
PostgreSQLのクエリの構文解析(Parsing)と意味解析(Semantic Analysis)
以下の記事を参考にします。
PostgreSQLはSQLのテキストそのままの状態ではDBで実行できません。
主に構文解析と意味解析を通してクエリーツリー(問い合わせツリー)を作成し、これにより実行します。
debug_print_parseをONにすると、SQLのテキストをDBが実行可能なクエリツリーの状態で出力します。
エイリアスでグルーピングするクエリのクエリツリー
2026-02-01 10:06:45 2026-02-01 01:06:45.297 UTC [63] LOG: statement: SELECT category as cg,COUNT(*)
2026-02-01 10:06:45 FROM hobbies
2026-02-01 10:06:45 GROUP BY cg;
2026-02-01 10:06:45 2026-02-01 01:06:45.297 UTC [63] LOG: parse tree:
2026-02-01 10:06:45 2026-02-01 01:06:45.297 UTC [63] DETAIL: {QUERY
2026-02-01 10:06:45 :commandType 1
2026-02-01 10:06:45 :querySource 0
2026-02-01 10:06:45 :canSetTag true
2026-02-01 10:06:45 :utilityStmt <>
2026-02-01 10:06:45 :resultRelation 0
2026-02-01 10:06:45 :hasAggs true
2026-02-01 10:06:45 :hasWindowFuncs false
2026-02-01 10:06:45 :hasTargetSRFs false
2026-02-01 10:06:45 :hasSubLinks false
2026-02-01 10:06:45 :hasDistinctOn false
2026-02-01 10:06:45 :hasRecursive false
2026-02-01 10:06:45 :hasModifyingCTE false
2026-02-01 10:06:45 :hasForUpdate false
2026-02-01 10:06:45 :hasRowSecurity false
2026-02-01 10:06:45 :hasGroupRTE true
2026-02-01 10:06:45 :isReturn false
2026-02-01 10:06:45 :cteList <>
2026-02-01 10:06:45 :rtable (
2026-02-01 10:06:45 {RANGETBLENTRY
2026-02-01 10:06:45 :alias <>
2026-02-01 10:06:45 :eref
2026-02-01 10:06:45 {ALIAS
2026-02-01 10:06:45 :aliasname hobbies
2026-02-01 10:06:45 :colnames ("id" "category" "name")
2026-02-01 10:06:45 }
2026-02-01 10:06:45 :rtekind 0
2026-02-01 10:06:45 :relid 16409
2026-02-01 10:06:45 :inh true
2026-02-01 10:06:45 :relkind r
2026-02-01 10:06:45 :rellockmode 1
2026-02-01 10:06:45 :perminfoindex 1
2026-02-01 10:06:45 :tablesample <>
2026-02-01 10:06:45 :lateral false
2026-02-01 10:06:45 :inFromCl true
2026-02-01 10:06:45 :securityQuals <>
2026-02-01 10:06:45 }
2026-02-01 10:06:45 {RANGETBLENTRY
2026-02-01 10:06:45 :alias <>
2026-02-01 10:06:45 :eref
2026-02-01 10:06:45 {ALIAS
2026-02-01 10:06:45 :aliasname *GROUP*
2026-02-01 10:06:45 :colnames ("cg")
2026-02-01 10:06:45 }
2026-02-01 10:06:45 :rtekind 9
2026-02-01 10:06:45 :groupexprs (
2026-02-01 10:06:45 {VAR
2026-02-01 10:06:45 :varno 1
2026-02-01 10:06:45 :varattno 2
2026-02-01 10:06:45 :vartype 25
2026-02-01 10:06:45 :vartypmod -1
2026-02-01 10:06:45 :varcollid 100
2026-02-01 10:06:45 :varnullingrels (b)
2026-02-01 10:06:45 :varlevelsup 0
2026-02-01 10:06:45 :varreturningtype 0
2026-02-01 10:06:45 :varnosyn 1
2026-02-01 10:06:45 :varattnosyn 2
2026-02-01 10:06:45 :location 7
2026-02-01 10:06:45 }
2026-02-01 10:06:45 )
2026-02-01 10:06:45 :lateral false
2026-02-01 10:06:45 :inFromCl false
2026-02-01 10:06:45 :securityQuals <>
2026-02-01 10:06:45 }
2026-02-01 10:06:45 )
2026-02-01 10:06:45 :rteperminfos (
2026-02-01 10:06:45 {RTEPERMISSIONINFO
2026-02-01 10:06:45 :relid 16409
2026-02-01 10:06:45 :inh true
2026-02-01 10:06:45 :requiredPerms 2
2026-02-01 10:06:45 :checkAsUser 0
2026-02-01 10:06:45 :selectedCols (b 9)
2026-02-01 10:06:45 :insertedCols (b)
2026-02-01 10:06:45 :updatedCols (b)
2026-02-01 10:06:45 }
2026-02-01 10:06:45 )
2026-02-01 10:06:45 :jointree
2026-02-01 10:06:45 {FROMEXPR
2026-02-01 10:06:45 :fromlist (
2026-02-01 10:06:45 {RANGETBLREF
2026-02-01 10:06:45 :rtindex 1
2026-02-01 10:06:45 }
2026-02-01 10:06:45 )
2026-02-01 10:06:45 :quals <>
2026-02-01 10:06:45 }
2026-02-01 10:06:45 :mergeActionList <>
2026-02-01 10:06:45 :mergeTargetRelation 0
2026-02-01 10:06:45 :mergeJoinCondition <>
2026-02-01 10:06:45 :targetList (
2026-02-01 10:06:45 {TARGETENTRY
2026-02-01 10:06:45 :expr
2026-02-01 10:06:45 {VAR
2026-02-01 10:06:45 :varno 2
2026-02-01 10:06:45 :varattno 1
2026-02-01 10:06:45 :vartype 25
2026-02-01 10:06:45 :vartypmod -1
2026-02-01 10:06:45 :varcollid 100
2026-02-01 10:06:45 :varnullingrels (b)
2026-02-01 10:06:45 :varlevelsup 0
2026-02-01 10:06:45 :varreturningtype 0
2026-02-01 10:06:45 :varnosyn 2
2026-02-01 10:06:45 :varattnosyn 1
2026-02-01 10:06:45 :location -1
2026-02-01 10:06:45 }
2026-02-01 10:06:45 :resno 1
2026-02-01 10:06:45 :resname cg
2026-02-01 10:06:45 :ressortgroupref 1
2026-02-01 10:06:45 :resorigtbl 16409
2026-02-01 10:06:45 :resorigcol 2
2026-02-01 10:06:45 :resjunk false
2026-02-01 10:06:45 }
2026-02-01 10:06:45 {TARGETENTRY
2026-02-01 10:06:45 :expr
2026-02-01 10:06:45 {AGGREF
2026-02-01 10:06:45 :aggfnoid 2803
2026-02-01 10:06:45 :aggtype 20
2026-02-01 10:06:45 :aggcollid 0
2026-02-01 10:06:45 :inputcollid 0
2026-02-01 10:06:45 :aggtranstype 0
2026-02-01 10:06:45 :aggargtypes <>
2026-02-01 10:06:45 :aggdirectargs <>
2026-02-01 10:06:45 :args <>
2026-02-01 10:06:45 :aggorder <>
2026-02-01 10:06:45 :aggdistinct <>
2026-02-01 10:06:45 :aggfilter <>
2026-02-01 10:06:45 :aggstar true
2026-02-01 10:06:45 :aggvariadic false
2026-02-01 10:06:45 :aggkind n
2026-02-01 10:06:45 :aggpresorted false
2026-02-01 10:06:45 :agglevelsup 0
2026-02-01 10:06:45 :aggsplit 0
2026-02-01 10:06:45 :aggno -1
2026-02-01 10:06:45 :aggtransno -1
2026-02-01 10:06:45 :location 22
2026-02-01 10:06:45 }
2026-02-01 10:06:45 :resno 2
2026-02-01 10:06:45 :resname count
2026-02-01 10:06:45 :ressortgroupref 0
2026-02-01 10:06:45 :resorigtbl 0
2026-02-01 10:06:45 :resorigcol 0
2026-02-01 10:06:45 :resjunk false
2026-02-01 10:06:45 }
2026-02-01 10:06:45 )
2026-02-01 10:06:45 :override 0
2026-02-01 10:06:45 :onConflict <>
2026-02-01 10:06:45 :returningOldAlias <>
2026-02-01 10:06:45 :returningNewAlias <>
2026-02-01 10:06:45 :returningList <>
2026-02-01 10:06:45 :groupClause (
2026-02-01 10:06:45 {SORTGROUPCLAUSE
2026-02-01 10:06:45 :tleSortGroupRef 1
2026-02-01 10:06:45 :eqop 98
2026-02-01 10:06:45 :sortop 664
2026-02-01 10:06:45 :reverse_sort false
2026-02-01 10:06:45 :nulls_first false
2026-02-01 10:06:45 :hashable true
2026-02-01 10:06:45 }
2026-02-01 10:06:45 )
2026-02-01 10:06:45 :groupDistinct false
2026-02-01 10:06:45 :groupingSets <>
2026-02-01 10:06:45 :havingQual <>
2026-02-01 10:06:45 :windowClause <>
2026-02-01 10:06:45 :distinctClause <>
2026-02-01 10:06:45 :sortClause <>
2026-02-01 10:06:45 :limitOffset <>
2026-02-01 10:06:45 :limitCount <>
2026-02-01 10:06:45 :limitOption 0
2026-02-01 10:06:45 :rowMarks <>
2026-02-01 10:06:45 :setOperations <>
2026-02-01 10:06:45 :constraintDeps <>
2026-02-01 10:06:45 :withCheckOptions <>
2026-02-01 10:06:45 :stmt_location 0
2026-02-01 10:06:45 :stmt_len 55
2026-02-01 10:06:45 }
2026-02-01 10:06:45
2026-02-01 10:06:45 2026-02-01 01:06:45.297 UTC [63] STATEMENT: SELECT category as cg,COUNT(*)
2026-02-01 10:06:45 FROM hobbies
2026-02-01 10:06:45 GROUP BY cg;
以下がselect category as cgの部分です。
TARGETENTRY
:resno 1
:resname cg
:ressortgroupref 1
resno = 1 -> SELECT 句の 1番目
resname = cg -> エイリアス名
ressortgroupref = 1 -> GROUP BYの参照ID
以下がGROUP BY cgの部分です。
:groupClause (
{SORTGROUPCLAUSE
:tleSortGroupRef 1
}
)
tleSortGroupRef = 1
SELECT 側の ressortgroupref = 1 と一致
次にカラム名でグルーピングするクエリツリーを見ます。
カラム名でグルーピングするクエリツリー
2026-02-01 10:38:48 2026-02-01 01:38:48.551 UTC [63] LOG: statement: SELECT category, COUNT(*)
2026-02-01 10:38:48 FROM hobbies
2026-02-01 10:38:48 GROUP BY category;
2026-02-01 10:38:48 2026-02-01 01:38:48.552 UTC [63] LOG: parse tree:
2026-02-01 10:38:48 2026-02-01 01:38:48.552 UTC [63] DETAIL: {QUERY
2026-02-01 10:38:48 :commandType 1
2026-02-01 10:38:48 :querySource 0
2026-02-01 10:38:48 :canSetTag true
2026-02-01 10:38:48 :utilityStmt <>
2026-02-01 10:38:48 :resultRelation 0
2026-02-01 10:38:48 :hasAggs true
2026-02-01 10:38:48 :hasWindowFuncs false
2026-02-01 10:38:48 :hasTargetSRFs false
2026-02-01 10:38:48 :hasSubLinks false
2026-02-01 10:38:48 :hasDistinctOn false
2026-02-01 10:38:48 :hasRecursive false
2026-02-01 10:38:48 :hasModifyingCTE false
2026-02-01 10:38:48 :hasForUpdate false
2026-02-01 10:38:48 :hasRowSecurity false
2026-02-01 10:38:48 :hasGroupRTE true
2026-02-01 10:38:48 :isReturn false
2026-02-01 10:38:48 :cteList <>
2026-02-01 10:38:48 :rtable (
2026-02-01 10:38:48 {RANGETBLENTRY
2026-02-01 10:38:48 :alias <>
2026-02-01 10:38:48 :eref
2026-02-01 10:38:48 {ALIAS
2026-02-01 10:38:48 :aliasname hobbies
2026-02-01 10:38:48 :colnames ("id" "category" "name")
2026-02-01 10:38:48 }
2026-02-01 10:38:48 :rtekind 0
2026-02-01 10:38:48 :relid 16409
2026-02-01 10:38:48 :inh true
2026-02-01 10:38:48 :relkind r
2026-02-01 10:38:48 :rellockmode 1
2026-02-01 10:38:48 :perminfoindex 1
2026-02-01 10:38:48 :tablesample <>
2026-02-01 10:38:48 :lateral false
2026-02-01 10:38:48 :inFromCl true
2026-02-01 10:38:48 :securityQuals <>
2026-02-01 10:38:48 }
2026-02-01 10:38:48 {RANGETBLENTRY
2026-02-01 10:38:48 :alias <>
2026-02-01 10:38:48 :eref
2026-02-01 10:38:48 {ALIAS
2026-02-01 10:38:48 :aliasname *GROUP*
2026-02-01 10:38:48 :colnames ("category")
2026-02-01 10:38:48 }
2026-02-01 10:38:48 :rtekind 9
2026-02-01 10:38:48 :groupexprs (
2026-02-01 10:38:48 {VAR
2026-02-01 10:38:48 :varno 1
2026-02-01 10:38:48 :varattno 2
2026-02-01 10:38:48 :vartype 25
2026-02-01 10:38:48 :vartypmod -1
2026-02-01 10:38:48 :varcollid 100
2026-02-01 10:38:48 :varnullingrels (b)
2026-02-01 10:38:48 :varlevelsup 0
2026-02-01 10:38:48 :varreturningtype 0
2026-02-01 10:38:48 :varnosyn 1
2026-02-01 10:38:48 :varattnosyn 2
2026-02-01 10:38:48 :location 7
2026-02-01 10:38:48 }
2026-02-01 10:38:48 )
2026-02-01 10:38:48 :lateral false
2026-02-01 10:38:48 :inFromCl false
2026-02-01 10:38:48 :securityQuals <>
2026-02-01 10:38:48 }
2026-02-01 10:38:48 )
2026-02-01 10:38:48 :rteperminfos (
2026-02-01 10:38:48 {RTEPERMISSIONINFO
2026-02-01 10:38:48 :relid 16409
2026-02-01 10:38:48 :inh true
2026-02-01 10:38:48 :requiredPerms 2
2026-02-01 10:38:48 :checkAsUser 0
2026-02-01 10:38:48 :selectedCols (b 9)
2026-02-01 10:38:48 :insertedCols (b)
2026-02-01 10:38:48 :updatedCols (b)
2026-02-01 10:38:48 }
2026-02-01 10:38:48 )
2026-02-01 10:38:48 :jointree
2026-02-01 10:38:48 {FROMEXPR
2026-02-01 10:38:48 :fromlist (
2026-02-01 10:38:48 {RANGETBLREF
2026-02-01 10:38:48 :rtindex 1
2026-02-01 10:38:48 }
2026-02-01 10:38:48 )
2026-02-01 10:38:48 :quals <>
2026-02-01 10:38:48 }
2026-02-01 10:38:48 :mergeActionList <>
2026-02-01 10:38:48 :mergeTargetRelation 0
2026-02-01 10:38:48 :mergeJoinCondition <>
2026-02-01 10:38:48 :targetList (
2026-02-01 10:38:48 {TARGETENTRY
2026-02-01 10:38:48 :expr
2026-02-01 10:38:48 {VAR
2026-02-01 10:38:48 :varno 2
2026-02-01 10:38:48 :varattno 1
2026-02-01 10:38:48 :vartype 25
2026-02-01 10:38:48 :vartypmod -1
2026-02-01 10:38:48 :varcollid 100
2026-02-01 10:38:48 :varnullingrels (b)
2026-02-01 10:38:48 :varlevelsup 0
2026-02-01 10:38:48 :varreturningtype 0
2026-02-01 10:38:48 :varnosyn 2
2026-02-01 10:38:48 :varattnosyn 1
2026-02-01 10:38:48 :location -1
2026-02-01 10:38:48 }
2026-02-01 10:38:48 :resno 1
2026-02-01 10:38:48 :resname category
2026-02-01 10:38:48 :ressortgroupref 1
2026-02-01 10:38:48 :resorigtbl 16409
2026-02-01 10:38:48 :resorigcol 2
2026-02-01 10:38:48 :resjunk false
2026-02-01 10:38:48 }
2026-02-01 10:38:48 {TARGETENTRY
2026-02-01 10:38:48 :expr
2026-02-01 10:38:48 {AGGREF
2026-02-01 10:38:48 :aggfnoid 2803
2026-02-01 10:38:48 :aggtype 20
2026-02-01 10:38:48 :aggcollid 0
2026-02-01 10:38:48 :inputcollid 0
2026-02-01 10:38:48 :aggtranstype 0
2026-02-01 10:38:48 :aggargtypes <>
2026-02-01 10:38:48 :aggdirectargs <>
2026-02-01 10:38:48 :args <>
2026-02-01 10:38:48 :aggorder <>
2026-02-01 10:38:48 :aggdistinct <>
2026-02-01 10:38:48 :aggfilter <>
2026-02-01 10:38:48 :aggstar true
2026-02-01 10:38:48 :aggvariadic false
2026-02-01 10:38:48 :aggkind n
2026-02-01 10:38:48 :aggpresorted false
2026-02-01 10:38:48 :agglevelsup 0
2026-02-01 10:38:48 :aggsplit 0
2026-02-01 10:38:48 :aggno -1
2026-02-01 10:38:48 :aggtransno -1
2026-02-01 10:38:48 :location 17
2026-02-01 10:38:48 }
2026-02-01 10:38:48 :resno 2
2026-02-01 10:38:48 :resname count
2026-02-01 10:38:48 :ressortgroupref 0
2026-02-01 10:38:48 :resorigtbl 0
2026-02-01 10:38:48 :resorigcol 0
2026-02-01 10:38:48 :resjunk false
2026-02-01 10:38:48 }
2026-02-01 10:38:48 )
2026-02-01 10:38:48 :override 0
2026-02-01 10:38:48 :onConflict <>
2026-02-01 10:38:48 :returningOldAlias <>
2026-02-01 10:38:48 :returningNewAlias <>
2026-02-01 10:38:48 :returningList <>
2026-02-01 10:38:48 :groupClause (
2026-02-01 10:38:48 {SORTGROUPCLAUSE
2026-02-01 10:38:48 :tleSortGroupRef 1
2026-02-01 10:38:48 :eqop 98
2026-02-01 10:38:48 :sortop 664
2026-02-01 10:38:48 :reverse_sort false
2026-02-01 10:38:48 :nulls_first false
2026-02-01 10:38:48 :hashable true
2026-02-01 10:38:48 }
2026-02-01 10:38:48 )
2026-02-01 10:38:48 :groupDistinct false
2026-02-01 10:38:48 :groupingSets <>
2026-02-01 10:38:48 :havingQual <>
2026-02-01 10:38:48 :windowClause <>
2026-02-01 10:38:48 :distinctClause <>
2026-02-01 10:38:48 :sortClause <>
2026-02-01 10:38:48 :limitOffset <>
2026-02-01 10:38:48 :limitCount <>
2026-02-01 10:38:48 :limitOption 0
2026-02-01 10:38:48 :rowMarks <>
2026-02-01 10:38:48 :setOperations <>
2026-02-01 10:38:48 :constraintDeps <>
2026-02-01 10:38:48 :withCheckOptions <>
2026-02-01 10:38:48 :stmt_location 0
2026-02-01 10:38:48 :stmt_len 56
2026-02-01 10:38:48 }
2026-02-01 10:38:48
2026-02-01 10:38:48 2026-02-01 01:38:48.552 UTC [63] STATEMENT: SELECT category, COUNT(*)
2026-02-01 10:38:48 FROM hobbies
2026-02-01 10:38:48 GROUP BY category;
select categoryの部分は以下です
{TARGETENTRY
:resno 1
:resname category
:ressortgroupref 1
}
resno = 1 -> SELECT 句の 1番目
resname = category -> カラム名 category
ressortgroupref = 1 -> GROUP BYの参照ID
GROUP BY categoryの部分は以下です
:groupClause (
{SORTGROUPCLAUSE
:tleSortGroupRef 1
}
)
tleSortGroupRef = 1
SELECT 側の ressortgroupref = 1 と一致
エイリアスでグルーピングした時と、カラム名指定でグルーピングした時では
違うのはresnameだけで、
resno = 1
ressortgroupref = 1
tleSortGroupRef = 1
は同じでした。
よってPostgreSQLは以下のように解析したことがわかります。
GROUP BY cg
→ cg は SELECT の 1番目だな
→ じゃあ 1番の TargetEntry でグルーピングしよう
まとめ
SQLでよく言われる
FROM → WHERE → GROUP BY → SELECT → ORDER BY → LIMIT
は論理的な実行順序であり、内部実装そのものではありません。
PostgreSQLでは、クエリの実行前に
構文解析(Parsing)・意味解析(Semantic Analysis)が行われ、
その段階で SELECT 句の TargetEntry(番号)が確定します。
GROUP BY 句は、実行時に列名やエイリアスを解釈しているのではなく、
構文解析・意味解析フェーズで確定した TargetEntry を参照して解決されます。
そのため
GROUP BY category と GROUP BY cg(category のエイリアス) は
同じ TargetEntry を指し、同一扱いになります。
この挙動は debug_print_parse を用いたクエリツリーのログから、
ressortgroupref と tleSortGroupRef が一致していることとして確認できます。
最後に
- 普段はORMでクエリを実行しているので、意識できていなかったですが裏側の処理の概要をつかむことができました
- SQL周りの理解を引き続き深めます
参考
https://www.postgresql.org/docs/18/sql-select.html#:~:text=GROUP%20BY%20will,output%20column%20name.


