Skip to content

Fix COUNT bug when pulling up scalar subqueries (fallback planner)#397

Draft
Alena0704 wants to merge 1 commit into
OPENGPDB_STABLEfrom
count-aggr-first-fallback
Draft

Fix COUNT bug when pulling up scalar subqueries (fallback planner)#397
Alena0704 wants to merge 1 commit into
OPENGPDB_STABLEfrom
count-aggr-first-fallback

Conversation

@Alena0704

@Alena0704 Alena0704 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

It is the same apache/cloudberry#1836 but I couldn't continue fixing there because of lack of access to the initial branch.

A correlated subquery like

select ... from t1 where t1.a > (select count(*) from t2 where t2.a = t1.d)

must return COUNT = 0 (not NULL) for outer rows that have no match. The fallback planner pulled such subqueries into an INNER join, which dropped every no-match row -- the classic COUNT bug.

PR #376 (df87bf0) turned the join into a LEFT join and wrapped the aggregate in COALESCE(agg, default) with COUNT -> 0. But the comparison stayed on the join itself (a "Join Filter"), so a matched row that failed the comparison was treated as unmatched, null-extended, and let back in by the COALESCE default -- e.g. count(*) returned 100 instead of 99.

Fix: when the pulled-up join is LEFT, evaluate the comparison ABOVE the join (wrap it in a FromExpr) instead of as the join condition. The INNER path is unchanged.

Tests in subselect_gp cover COUNT and mixed COUNT+other aggregate expressions on both the optimizer=off and ORCA->planner fallback paths.

@Alena0704 Alena0704 force-pushed the count-aggr-first-fallback branch from e640835 to 68c09fb Compare July 2, 2026 20:05
@Alena0704 Alena0704 marked this pull request as draft July 2, 2026 20:12
@Alena0704 Alena0704 force-pushed the count-aggr-first-fallback branch 2 times, most recently from c00def6 to 9ffda49 Compare July 2, 2026 21:05
A correlated subquery like

    select ... from t1 where t1.a > (select count(*) from t2 where t2.a = t1.d)

must return COUNT = 0 (not NULL) for outer rows that have no match. The
fallback planner pulled such subqueries into an INNER join, which dropped
every no-match row -- the classic COUNT bug.

PR #376 (df87bf0) turned the join into a LEFT join and wrapped the aggregate
in COALESCE(agg, default) with COUNT -> 0. Two problems with that alone:

  * The comparison stayed on the join itself (a "Join Filter"), so a matched
    row that failed the comparison was treated as unmatched, null-extended,
    and let back in by the COALESCE default -- e.g. count(*) returned 100
    instead of 99. Fixed here in pull_up_sublinks_qual_recurse(): when the
    pulled-up join is LEFT, evaluate the comparison ABOVE the join (wrap it in
    a FromExpr) rather than as the join condition.

  * It switched to a LEFT join for every COUNT-containing subquery, even where
    an INNER join was already correct (e.g. "1 = (select count(*) ...)", whose
    no-match value 0 can never satisfy the predicate), churning many unrelated
    plans. Narrowed here: only use the LEFT join + COALESCE when a no-match row
    could actually survive, i.e. when "outerExpr OP default" does not fold to a
    constant FALSE/NULL. Otherwise keep the INNER join unchanged.

The INNER path is otherwise untouched.

Tests in subselect_gp cover COUNT and mixed COUNT+other aggregate expressions
on both the optimizer=off and ORCA->planner fallback paths.

Co-Authored-By: excaliiibur <excaliiibur@foxmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant