branch-4.1:[fix](subquery): avoid join amplification in WinMagic rewrite rule - #67288
Open
starocean999 wants to merge 2 commits into
Open
branch-4.1:[fix](subquery): avoid join amplification in WinMagic rewrite rule#67288starocean999 wants to merge 2 commits into
starocean999 wants to merge 2 commits into
Conversation
…pache#63763) Problem Summary: The `AggScalarSubQueryToWindowFunction` rewrite rule (WinMagic) rewrites a correlated scalar subquery like: ```sql SELECT d.did, f.id, f.k, f.v FROM fact f, dim d WHERE f.k = d.k AND f.v * 2 > ( SELECT SUM(f2.v) FROM fact f2 WHERE f2.k = d.k ) ``` into a window-function plan: ``` Filter(f.v * 2 > SUM(v) OVER (PARTITION BY d.k)) Window(SUM(v) OVER (PARTITION BY d.k)) CrossJoin(Scan(fact f), Scan(dim d)) ``` This rewrite had three correctness issues: --- **Bug 1 — Join amplification when the correlated key is not unique** ```sql -- dim has DUPLICATE KEY(did), so k is NOT unique and can be NULL CREATE TABLE dim (did INT, k INT, tag INT) ENGINE=OLAP DUPLICATE KEY(did) ...; -- data: two rows with the same k=1 INSERT INTO dim VALUES (10, 1, 1), (11, 1, 1); -- fact and dim are joined on f.k = d.k SELECT d.did, f.id, f.k, f.v FROM fact f, dim d WHERE f.k = d.k AND f.v * 2 > ( SELECT SUM(f2.v) FROM fact f2 WHERE f2.k = d.k ); ``` Before the fix, the rule **did not check** whether the correlated outer-only table's key (`d.k`) is unique. Since `dim` is a DUPLICATE KEY table, `k=1` has two rows. After the CrossJoin, each fact row with `k=1` joins with both dim rows, producing 2× more output rows than the original correlated scalar subquery (which is evaluated per outer row). The window function then sees this inflated row set, producing wrong aggregate values. **Fix:** Added `checkUniqueCorrelatedTable()` which verifies the correlated outer-only table's key is unique & non-null via `DataTrait.isUniqueAndNotNull()`. This covers OLAP UNIQUE_KEYS metadata, explicit `UNIQUE` constraints, and rejects nullable keys. Queries against non-unique-keyed tables are left as original scalar subqueries. --- **Bug 2 — Inner filter split by PushDownFilterThroughProject causes rewrite to fail** ```sql -- inner subquery has both a correlated predicate and a non-correlated predicate SELECT d.did, f.id, f.k, f.v FROM fact f, dim_unique d WHERE f.k = d.k AND f.v < 10 AND f.v * 2 > ( SELECT SUM(f2.v) FROM fact f2 WHERE f2.k = d.k -- correlated, cannot push through project AND f2.v < 10 -- non-correlated, can push through project ); ``` The optimizer's `PushDownFilterThroughProject` splits the inner `WHERE f2.k = d.k AND f2.v < 10` into two separate `LogicalFilter` nodes: - `Filter(k = d.k)` stays above the project (references correlated slot `d.k`, not in project output) - `Filter(v < 10)` is pushed below the project (only references `v`, which is in the project output) The old `checkFilter()` required **exactly one** inner `LogicalFilter` and returned false when seeing two, causing the rule to miss this valid rewrite. **Fix:** `checkFilter()` now collects conjuncts from **all** inner `LogicalFilter` nodes rather than requiring exactly one. --- **Bug 3 — Matched inner-filter predicates incorrectly placed above the window** ```sql -- f.v < 10 appears both in the outer WHERE and the inner WHERE SELECT d.did, f.id, f.k, f.v FROM fact f, dim_unique d WHERE f.k = d.k AND f.v < 10 AND f.v * 2 > ( SELECT SUM(f2.v) FROM fact f2 WHERE f2.k = d.k AND f2.v < 10 ); ``` The predicate `f.v < 10` is **semantically part of the inner aggregate's filter** — the original scalar subquery only sums `f2.v` where `v < 10`. However, the old rewrite placed this conjunct **above** the window: ``` -- Wrong: f.v < 10 above the window lets the window see ALL rows Filter(f.v < 10 AND f.v * 2 > SUM(v) OVER (...)) Window(SUM(v) OVER (PARTITION BY d.k)) -- aggregates all rows, not just v<10 CrossJoin(Scan(fact f), Scan(dim_unique d)) ``` This means the window function computes `SUM(v)` over **all** fact rows per key (e.g., `v=5+7+6+4+10+6+8`), instead of only rows where `v < 10` (e.g., `v=5+7+6+4+6+8`). The comparison `f.v * 2 > sum` then uses the wrong aggregate, potentially returning incorrect rows. **Fix:** `checkFilter()` now tracks which outer conjuncts were matched against inner subquery filter conjuncts. `rewrite()` places these matched conjuncts **below** the window, preserving the inner aggregate's filter semantic: ``` -- Correct: f.v < 10 below the window restricts the window's input Filter(f.v * 2 > SUM(v) OVER (...)) Window(SUM(v) OVER (PARTITION BY d.k)) Filter(f.v < 10) -- restricts rows seen by the window CrossJoin(Scan(fact f), Scan(dim_unique d)) ``` --- - `checkUniqueCorrelatedTable()` — validates correlated key uniqueness via `DataTrait.isUniqueAndNotNull()` - Fix `checkFilter()` — collect conjuncts from ALL inner filters, track matched conjuncts - Enhance `rewrite()` — classify conjuncts: matched inner-filter → below window, shared-table → above, volatile → above, outer-only → below - Add `stripOuterFilters()` and `ensureProjectOutput()` helpers - 13 unit tests + 6 regression cases with result and plan-shape verification Scalar correlated subquery to window function rewrite is now correct: non-unique keys are rejected, split inner filters are handled, and inner-filter predicates are placed below the window.
Contributor
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
Contributor
Author
|
run buildall |
Contributor
FE UT Coverage ReportIncrement line coverage |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
pick #63763
Related PR: #xxx
Problem Summary:
Release note
None
Check List (For Author)
Test
Behavior changed:
Does this need documentation?
Check List (For Reviewer who merge this PR)