Re: Inconsistent Behavior of GROUP BY ROLLUP in v17 vs master
Richard Guo <guofenglinux@gmail.com>
From: Richard Guo <guofenglinux@gmail.com>
To: Tom Lane <tgl@sss.pgh.pa.us>
Cc: David Rowley <dgrowleyml@gmail.com>, 邱宇航 <iamqyh@gmail.com>, Bruce Momjian <bruce@momjian.us>, PostgreSQL-development <pgsql-hackers@postgresql.org>
Date: 2025-09-24T03:10:08Z
Lists: pgsql-hackers
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Fix test case from 40c242830
- ee49f2cf447a 18.1 landed
-
Fix pushdown of degenerate HAVING clauses
- 40c2428307b8 18.1 landed
- 18d261409348 19 (unreleased) landed
-
Allow pushdown of HAVING clauses with grouping sets
- 67a54b9e83d3 18.0 cited
-
Mark expressions nullable by grouping sets
- f5050f795aea 18.0 cited
On Wed, Sep 24, 2025 at 10:23 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Yeah, I don't think the havingClause being constant-false is the
> key point here. I've not looked closely at 67a54b9e, but I suspect
> that anything Var-free is potentially an issue. Or it might even
> fail for something that has Vars but is non-strict.
>
> The core of the problem though is that the aggregation node will
> issue an all-nulls row that did not come from its input, and we
> have to be sure that the HAVING clause is properly evaluated
> for that row.
I think the issue is that an empty grouping set can produce a row out
of nowhere, and pushing down HAVING clauses in this case may cause us
to fail to filter out that row.
Currently, non-variable-free HAVING clauses aren't pushed down when
there is an empty grouping set, because the empty grouping set would
nullify the vars referenced in those clauses, and we have logic in
place to prevent their pushdown.
However, we (I) overlooked variable-free HAVING clauses. If there are
only empty grouping sets, this is not a problem since a copy of the
HAVING clauses is retained in HAVING. The issue arises when there are
both nonempty and empty grouping sets.
In summary, the problem occurs when both of the following conditions
are met: 1) there are both nonempty and empty grouping sets, 2) there
are variable-free HAVING clauses.
I think the following change fixes this problem.
foreach(l, (List *) parse->havingQual)
{
Node *havingclause = (Node *) lfirst(l);
+ Relids having_relids;
if (contain_agg_clause(havingclause) ||
contain_volatile_functions(havingclause) ||
contain_subplans(havingclause) ||
(parse->groupClause && parse->groupingSets &&
- bms_is_member(root->group_rtindex, pull_varnos(root,
havingclause))))
+ ((having_relids = pull_varnos(root, havingclause)) == NULL ||
+ bms_is_member(root->group_rtindex, having_relids))))
{
/* keep it in HAVING */
newHaving = lappend(newHaving, havingclause);
This change essentially prevents variable-free HAVING clauses from
being pushed down when there are any nonempty grouping sets. Of
course, this could be done more precisely -- for example, we could
restrict the prevention only to cases where empty grouping sets are
also present, but I'm not sure it's worth the effort.
- Richard