Re: BUG #19106: Potential regression with CTE materialization planning in Postgres 18

From: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
To: Richard Guo <guofenglinux(at)gmail(dot)com>
Cc: Vik Fearing <vik(at)postgresfriends(dot)org>, Kamil Monicz <kamil(at)monicz(dot)dev>, pgsql-bugs(at)lists(dot)postgresql(dot)org, Peter Eisentraut <peter(at)eisentraut(dot)org>
Subject: Re: BUG #19106: Potential regression with CTE materialization planning in Postgres 18
Date: 2025-11-12 19:32:53
Message-ID: 2394348.1762975973@sss.pgh.pa.us
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Richard Guo <guofenglinux(at)gmail(dot)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

In response to

Browse pgsql-bugs by date

  From Date Subject
Previous Message Tom Lane 2025-11-12 18:27:01 Re: RLS creates inaccurate limit and offset results