0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PostgreSQLのGROUP BY句にエイリアスが使える理由について理解する 🐘

Last updated at Posted at 2026-02-01

概要
本記事では、SQLの論理的な実行順序の説明を前提とした上で、PostgreSQLでGROUP BYにエイリアスが指定できる理由を内部の解析から見ていきます。

まとめ

SQLでよく言われる
FROM → WHERE → GROUP BY → SELECT → ORDER BY → LIMIT
は論理的な実行順序であり、内部実装そのものではありません。

PostgreSQLでは、クエリの実行前に
構文解析(Parsing)・意味解析(Semantic Analysis)が行われ、
その段階で SELECT 句の TargetEntry(番号)が確定します。

GROUP BY 句は、実行時に列名やエイリアスを解釈しているのではなく、
構文解析・意味解析フェーズで確定した TargetEntry を参照して解決されます。

そのため
GROUP BY categoryGROUP BY cg(category のエイリアス)
同じ TargetEntry を指し、同一扱いになります。

この挙動は debug_print_parse を用いたクエリツリーのログから、
ressortgroupreftleSortGroupRef が一致していることとして確認できます。

以下のスライドでも簡単にまとめていますのでご覧ください!

背景

上司に以下の記事をおすすめされたので理解したいと思います。

ここで言われている実行順序とはなんだろうなと思っていて、以下の本で実行順序について書かれていたので紹介します。

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だと以下のようになります。

image.png

image.png

確かにこの記述で問題なく実行できることがわかりました。

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文の内部的な実行順序

  1. WITH内のSELECT
  2. FROM
  3. WHERE
  4. GROUP BY
  5. HAVING
  6. SELECT
    • 列に対する関数やDISTINCTなど
  7. UNION、INTERSECT、EXCEPT
  8. ORDER BY
  9. 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で実行できません。
主に構文解析と意味解析を通してクエリーツリー(問い合わせツリー)を作成し、これにより実行します。

image.png

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 categoryGROUP BY cg(category のエイリアス)
同じ TargetEntry を指し、同一扱いになります。

この挙動は debug_print_parse を用いたクエリツリーのログから、
ressortgroupreftleSortGroupRef が一致していることとして確認できます。

最後に

  • 普段はORMでクエリを実行しているので、意識できていなかったですが裏側の処理の概要をつかむことができました
  • SQL周りの理解を引き続き深めます

参考

https://www.postgresql.org/docs/18/sql-select.html#:~:text=GROUP%20BY%20will,output%20column%20name.

https://www.postgresql.org/docs/18/queries-table-expressions.html#QUERIES-GROUP:~:text=In%20strict%20SQL%2C%20GROUP%20BY%20can%20only%20group%20by%20columns%20of%20the%20source%20table%20but%20PostgreSQL%20extends%20this%20to%20also%20allow%20GROUP%20BY%20to%20group%20by%20columns%20in%20the%20select%20list.%20Grouping%20by%20value%20expressions%20instead%20of%20simple%20column%20names%20is%20also%20allowed.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?