Re: BUG #19106: Potential regression with CTE materialization planning in Postgres 18
Tom Lane <tgl@sss.pgh.pa.us>
From: Tom Lane <tgl@sss.pgh.pa.us>
To: Richard Guo <guofenglinux@gmail.com>
Cc: Vik Fearing <vik@postgresfriends.org>, Kamil Monicz <kamil@monicz.dev>,
pgsql-bugs@lists.postgresql.org,
Peter Eisentraut <peter@eisentraut.org>
Date: 2025-11-12T19:32:53Z
Lists: pgsql-bugs
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Don't allow CTEs to determine semantic levels of aggregates.
- b85397481ce3 14.21 landed
- 9f5a58aacf65 15.16 landed
- 35b5c62c3ad9 19 (unreleased) landed
- 1c8c3206f4e0 16.12 landed
- 12bc3291772e 18.2 landed
- 075a763e2d42 17.8 landed
-
Calculate agglevelsup correctly when Aggref contains a CTE.
- b0cc0a71e0a0 19 (unreleased) cited
Richard Guo <guofenglinux@gmail.com> writes: > I played with this patch, but I couldn't quite wrap my head around the > expected behavior of using subqueries as arguments to aggregate > functions. The outputs of the following queries are confusing to me. > ... > Query 2: > select (select sum((select a from t t1 where a = t2.a or true limit > 1))) from t t2; > sum > ----- > 2 > (1 row) > I don't quite understand the output of Query 2. The subquery is now > correlated with the outer table t2, but I believe it's still in the > same form as Query 1, so I would expect it to also produce one output > row per table row. I believe the critical point about Q2 is that the presence of the reference to t2.a causes the sum() aggregate to be assigned to the outer query level. Now, that outer query is an aggregation query so it will produce only one row, aggregated over both rows of t2 (for each of which, the bottom sub-select produces the value "1"). I've never totally understood the rationale for the SQL standard to assign aggregates to outer query levels, but it's definitely their fault not ours. > Query 3: > with t as (select a from (values (1), (2)) as v(a)) > select (select sum((select a from t t1 where a = t2.a or true limit > 1))) from t t2; > sum > ----- > 1 > 1 > (2 rows) Actually, as of HEAD we produce: regression=# with t as (select a from (values (1), (2)) as v(a)) select (select sum((select a from t t1 where a = t2.a or true limit 1))) from t t2; sum ----- 2 (1 row) and it's the same if you remove the t2.a reference: regression=# with t as (select a from (values (1), (2)) as v(a)) select (select sum((select a from t t1 where true limit 1))) from t t2; sum ----- 2 (1 row) In both cases we assign the aggregate to the outer-level SELECT. I assume you were testing with my patch, which forces the sum() to be level zero, that is belonging to the intermediate sub-select. > Query 3 replaces the physical table with a CTE that produces the same > logical table content, so I would expect the query's output to remain > unchanged. So the differing outputs of Query 2 and Query 3 are also > confusing to me. The sticky point here is that a CTE reference isn't quite as absolute as a physical-table reference: the CTE name only has meaning within a portion of the query. So the problem that b0cc0a71e tried to solve is "what do we do if the SQL-standard rules about semantic level of an aggregate would result in putting the aggregate outside of the scope of a CTE it references?" I suppose another answer would be to throw up our hands and give an error if that happens, rather than trying to fix the level assigned to the aggregate. regards, tom lane