From 3d816fb06b2731f5c26aed73b6dde9856b1d46a0 Mon Sep 17 00:00:00 2001 From: minghong Date: Mon, 13 Apr 2026 22:23:03 +0800 Subject: [PATCH 1/2] [fix](eagerAgg) Fix eager aggregation incorrectly pushing agg(literal) to nullable side of outer joins (#62107) ### What problem does this PR solve? Issue Number: close #xxx Problem Summary: The EagerAggRewriter only blocked count(*)/count(literal) from being pushed to the nullable side of outer joins (via instanceof Count check). But the same logic applies to ALL aggregate functions whose input slots do not reference columns from the target side, e.g. sum(2), min(1), max(3). For unmatched rows in outer joins, these aggregates should produce their literal-based result (e.g. sum(2) adds 2 per unmatched row). After incorrect pushdown to the nullable side, the pre-aggregated slot becomes NULL for unmatched rows, and sum(NULL)/min(NULL)/max(NULL) loses the contribution entirely. The fix generalizes the existing Count-only guard to all aggregate functions: for any agg function with no input slots from the target side, block pushdown to the nullable side of outer joins. agg(nullable_side_col) is still safe to push because NULL values are naturally handled by aggregates. --- .../eageraggregation/EagerAggRewriter.java | 47 ++++--- .../EagerAggRewriterTest.java | 55 ++++++++ .../data/nereids_p0/eager_agg/eager_agg.out | 56 +++++++++ .../nereids_p0/eager_agg/eager_agg.groovy | 117 ++++++++++++++++++ 4 files changed, 250 insertions(+), 25 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriter.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriter.java index 87050b5bbdfe22..50a9d0339e875b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriter.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriter.java @@ -28,7 +28,6 @@ import org.apache.doris.nereids.trees.expressions.Slot; import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction; -import org.apache.doris.nereids.trees.expressions.functions.agg.Count; import org.apache.doris.nereids.trees.expressions.functions.scalar.If; import org.apache.doris.nereids.trees.plans.JoinType; import org.apache.doris.nereids.trees.plans.Plan; @@ -133,34 +132,32 @@ public Plan visitLogicalJoin(LogicalJoin join, P } } - // Do not push count(*)/count(literal)/count(preserved_side_col) to the nullable side of outer joins. - // count(*) counts all physical rows, including null-extended rows from the outer join. - // After pushdown to the nullable side, unmatched rows produce NULL for the pre-aggregated count, - // and ifnull(sum(NULL), 0) = 0, which loses the count of unmatched rows. - // However, count(nullable_side_col) is safe to push down because for unmatched rows, - // nullable_side_col IS NULL, so the original count is 0, matching ifnull(sum(NULL), 0) = 0. + // Do not push agg(literal) or agg(preserved_side_col) to the nullable side of outer joins. + // Aggregates like count(*), sum(2), min(1) etc. aggregate over all physical rows, + // including null-extended rows from the outer join. + // After pushdown to the nullable side, unmatched rows produce NULL for the pre-aggregated value, + // losing the contribution of those rows (e.g. sum(2) should add 2 per unmatched row, + // but sum(NULL) skips them). + // However, agg(nullable_side_col) is safe to push down because for unmatched rows, + // nullable_side_col IS NULL, and the aggregate naturally handles NULL values correctly. if (!join.getJoinType().isInnerJoin() && !join.getJoinType().isCrossJoin()) { JoinType joinType = join.getJoinType(); + boolean leftIsNullable = joinType.isRightOuterJoin() || joinType.isFullOuterJoin(); + boolean rightIsNullable = joinType.isLeftOuterJoin() || joinType.isFullOuterJoin(); for (AggregateFunction aggFunc : context.getAggFunctions()) { - if (aggFunc instanceof Count) { - Set countInputSlots = aggFunc.getInputSlots(); - // Determine which side is nullable - boolean leftIsNullable = joinType.isRightOuterJoin() || joinType.isFullOuterJoin(); - boolean rightIsNullable = joinType.isLeftOuterJoin() || joinType.isFullOuterJoin(); - // Check if we're pushing to a nullable side without referencing its columns - if (toLeft && leftIsNullable) { - boolean hasLeftInput = countInputSlots.stream() - .anyMatch(slot -> join.left().getOutputSet().contains(slot)); - if (!hasLeftInput) { - toLeft = false; - } + Set inputSlots = aggFunc.getInputSlots(); + if (toLeft && leftIsNullable) { + boolean hasLeftInput = inputSlots.stream() + .anyMatch(slot -> join.left().getOutputSet().contains(slot)); + if (!hasLeftInput) { + toLeft = false; } - if (toRight && rightIsNullable) { - boolean hasRightInput = countInputSlots.stream() - .anyMatch(slot -> join.right().getOutputSet().contains(slot)); - if (!hasRightInput) { - toRight = false; - } + } + if (toRight && rightIsNullable) { + boolean hasRightInput = inputSlots.stream() + .anyMatch(slot -> join.right().getOutputSet().contains(slot)); + if (!hasRightInput) { + toRight = false; } } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriterTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriterTest.java index 4b0561ccd0f894..036c80af64c3c5 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriterTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/eageraggregation/EagerAggRewriterTest.java @@ -311,4 +311,59 @@ void testAsofJoinNotPushAgg() { connectContext.getSessionVariable().setDisableJoinReorder(false); } } + + @Test + void testNotPushAggLiteralToNullableSideOfOuterJoin() { + // sum(literal), min(literal), max(literal) aggregate over all physical rows, + // including null-extended rows from the outer join. + // Pushing to the nullable side loses the contribution of unmatched rows: + // original: sum(2) on unmatched row = 2 + // pushed: sum(NULL) skips the row (wrong!) + // So agg(literal) must NOT be pushed to the nullable side. + connectContext.getSessionVariable().setEagerAggregationMode(1); + connectContext.getSessionVariable().setDisableJoinReorder(true); + try { + // RIGHT JOIN: t1 is the nullable side (left side of RIGHT JOIN) + // sum(2) should NOT be pushed to t1 + String sql = "select sum(2), t2.id2 from t1 right join t2" + + " on t1.id1 = t2.id2 group by t2.id2"; + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .nonMatch(logicalJoin(logicalAggregate(), any())) + .printlnTree(); + + // LEFT JOIN: t2 is the nullable side (right side of LEFT JOIN) + // min(1) should NOT be pushed to t2 + sql = "select min(1), t1.id1 from t1 left join t2" + + " on t1.id1 = t2.id2 group by t1.id1"; + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .nonMatch(logicalJoin(any(), logicalAggregate())) + .printlnTree(); + + // RIGHT JOIN: max(3) should NOT be pushed to nullable left side + sql = "select max(3), t2.id2 from t1 right join t2" + + " on t1.id1 = t2.id2 group by t2.id2"; + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .nonMatch(logicalJoin(logicalAggregate(), any())) + .printlnTree(); + + // Verify agg(nullable_side_col) is still safe to push (no regression) + // max(t1.name) references the left (nullable) side, so push is allowed + sql = "select max(t1.name), t2.id2 from t1 right join t2" + + " on t1.id1 = t2.id2 group by t2.id2"; + PlanChecker.from(connectContext) + .analyze(sql) + .rewrite() + .matches(logicalAggregate(logicalProject(logicalJoin(logicalAggregate(), any())))) + .printlnTree(); + } finally { + connectContext.getSessionVariable().setEagerAggregationMode(0); + connectContext.getSessionVariable().setDisableJoinReorder(false); + } + } } diff --git a/regression-test/data/nereids_p0/eager_agg/eager_agg.out b/regression-test/data/nereids_p0/eager_agg/eager_agg.out index 731f2e9bd9eda7..ad5bf604100d83 100644 --- a/regression-test/data/nereids_p0/eager_agg/eager_agg.out +++ b/regression-test/data/nereids_p0/eager_agg/eager_agg.out @@ -311,3 +311,59 @@ Used: UnUsed: SyntaxError: leading({ ss broadcast dt } broadcast ws) Msg:can not find table: ws +-- !check_sum_literal_right_join_not_push -- +PhysicalResultSink +--hashAgg[GLOBAL] +----hashAgg[LOCAL] +------hashJoin[RIGHT_OUTER_JOIN] hashCondition=((a.val = c.val) and (b.id2 = c.id2)) otherCondition=() +--------hashJoin[RIGHT_OUTER_JOIN] hashCondition=((a.id = b.id)) otherCondition=() +----------PhysicalOlapScan[eager_agg_t1(a)] +----------PhysicalOlapScan[eager_agg_t2(b)] +--------PhysicalOlapScan[eager_agg_t3(c)] + +-- !check_sum_literal_left_join_not_push -- +PhysicalResultSink +--hashAgg[GLOBAL] +----hashAgg[LOCAL] +------hashJoin[LEFT_OUTER_JOIN] hashCondition=((date_dim.d_date_sk = store_sales.ss_sold_date_sk)) otherCondition=() +--------hashAgg[GLOBAL] +----------hashAgg[LOCAL] +------------PhysicalOlapScan[store_sales] +--------PhysicalOlapScan[date_dim] + +-- !check_min_literal_right_join_not_push -- +PhysicalResultSink +--hashAgg[GLOBAL] +----hashAgg[LOCAL] +------hashJoin[RIGHT_OUTER_JOIN] hashCondition=((a.val = c.val) and (b.id2 = c.id2)) otherCondition=() +--------hashJoin[RIGHT_OUTER_JOIN] hashCondition=((a.id = b.id)) otherCondition=() +----------PhysicalOlapScan[eager_agg_t1(a)] +----------PhysicalOlapScan[eager_agg_t2(b)] +--------PhysicalOlapScan[eager_agg_t3(c)] + +-- !check_max_literal_left_join_not_push -- +PhysicalResultSink +--hashAgg[GLOBAL] +----hashAgg[LOCAL] +------hashJoin[LEFT_OUTER_JOIN] hashCondition=((date_dim.d_date_sk = store_sales.ss_sold_date_sk)) otherCondition=() +--------hashAgg[GLOBAL] +----------hashAgg[LOCAL] +------------PhysicalOlapScan[store_sales] +--------PhysicalOlapScan[date_dim] + +-- !sum_literal_right_join_eager_off -- +\N 4 +10 2 + +-- !sum_literal_right_join_eager_on -- +\N 4 +10 2 + +-- !min_literal_right_join_eager_on -- +\N 1 +10 1 + +-- !max_literal_right_join_eager_on -- +\N 3 +10 3 + diff --git a/regression-test/suites/nereids_p0/eager_agg/eager_agg.groovy b/regression-test/suites/nereids_p0/eager_agg/eager_agg.groovy index 4f9653829b3954..0693aca6caffe2 100644 --- a/regression-test/suites/nereids_p0/eager_agg/eager_agg.groovy +++ b/regression-test/suites/nereids_p0/eager_agg/eager_agg.groovy @@ -411,4 +411,121 @@ suite("eager_agg") { ) t group by d_year; """ + + // ========================================================================= + // Tests for agg(literal) on nullable side of outer joins + // sum(literal), min(literal), max(literal) should NOT be pushed to the + // nullable side of outer joins because unmatched rows lose their contribution. + // ========================================================================= + + sql """ + drop table if exists eager_agg_t1; + drop table if exists eager_agg_t2; + drop table if exists eager_agg_t3; + + CREATE TABLE eager_agg_t1 ( + id INT NOT NULL, + val INT + ) DISTRIBUTED BY HASH(id) BUCKETS 1 + PROPERTIES ('replication_num' = '1'); + + CREATE TABLE eager_agg_t2 ( + id INT NOT NULL, + id2 INT NOT NULL + ) DISTRIBUTED BY HASH(id) BUCKETS 1 + PROPERTIES ('replication_num' = '1'); + + CREATE TABLE eager_agg_t3 ( + id2 INT NOT NULL, + val INT + ) DISTRIBUTED BY HASH(id2) BUCKETS 1 + PROPERTIES ('replication_num' = '1'); + + INSERT INTO eager_agg_t1 VALUES (1, 10); + INSERT INTO eager_agg_t2 VALUES (1, 100), (2, 200); + INSERT INTO eager_agg_t3 VALUES (100, 10), (200, 20), (300, 30); + """ + + // sum(literal) should NOT be pushed below RIGHT JOIN to the nullable left side + qt_check_sum_literal_right_join_not_push """ + explain shape plan + select /*+SET_VAR(eager_aggregation_mode=1, disable_join_reorder = true)*/ + a.val, sum(2) as s + from eager_agg_t1 as a + right join eager_agg_t2 as b on a.id = b.id + right join eager_agg_t3 as c on b.id2 = c.id2 and a.val = c.val + group by a.val; + """ + + // sum(literal) should NOT be pushed below LEFT JOIN to the nullable right side + qt_check_sum_literal_left_join_not_push """ + explain shape plan + select /*+SET_VAR(eager_aggregation_mode=1, disable_join_reorder = true)*/ + ss_sales_price, sum(2) as s + from store_sales + left join date_dim on d_date_sk = ss_sold_date_sk + group by ss_sales_price; + """ + + // min(literal) should NOT be pushed to nullable side of RIGHT JOIN + qt_check_min_literal_right_join_not_push """ + explain shape plan + select /*+SET_VAR(eager_aggregation_mode=1, disable_join_reorder = true)*/ + a.val, min(1) as m + from eager_agg_t1 as a + right join eager_agg_t2 as b on a.id = b.id + right join eager_agg_t3 as c on b.id2 = c.id2 and a.val = c.val + group by a.val; + """ + + // max(literal) should NOT be pushed to nullable side of LEFT JOIN + qt_check_max_literal_left_join_not_push """ + explain shape plan + select /*+SET_VAR(eager_aggregation_mode=1, disable_join_reorder = true)*/ + ss_sales_price, max(3) as m + from store_sales + left join date_dim on d_date_sk = ss_sold_date_sk + group by ss_sales_price; + """ + + // Execution tests: verify eager agg produces correct results for outer join + literal agg + order_qt_sum_literal_right_join_eager_off """ + select /*+SET_VAR(eager_aggregation_mode=-1)*/ /*+ leading(a b c) */ + a.val, sum(2) as s + from eager_agg_t1 as a + right join eager_agg_t2 as b on a.id = b.id + right join eager_agg_t3 as c on b.id2 = c.id2 and a.val = c.val + group by a.val + order by a.val; + """ + + order_qt_sum_literal_right_join_eager_on """ + select /*+SET_VAR(eager_aggregation_mode=1)*/ /*+ leading(a b c) */ + a.val, sum(2) as s + from eager_agg_t1 as a + right join eager_agg_t2 as b on a.id = b.id + right join eager_agg_t3 as c on b.id2 = c.id2 and a.val = c.val + group by a.val + order by a.val; + """ + + order_qt_min_literal_right_join_eager_on """ + select /*+SET_VAR(eager_aggregation_mode=1)*/ /*+ leading(a b c) */ + a.val, min(1) as m + from eager_agg_t1 as a + right join eager_agg_t2 as b on a.id = b.id + right join eager_agg_t3 as c on b.id2 = c.id2 and a.val = c.val + group by a.val + order by a.val; + """ + + order_qt_max_literal_right_join_eager_on """ + select /*+SET_VAR(eager_aggregation_mode=1)*/ /*+ leading(a b c) */ + a.val, max(3) as m + from eager_agg_t1 as a + right join eager_agg_t2 as b on a.id = b.id + right join eager_agg_t3 as c on b.id2 = c.id2 and a.val = c.val + group by a.val + order by a.val; + """ } From 4dd450313e74d9d5232a9f2eaeaeb989693c2eb1 Mon Sep 17 00:00:00 2001 From: englefly Date: Mon, 25 May 2026 10:10:12 +0800 Subject: [PATCH 2/2] shape --- .../data/nereids_p0/eager_agg/eager_agg.out | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/regression-test/data/nereids_p0/eager_agg/eager_agg.out b/regression-test/data/nereids_p0/eager_agg/eager_agg.out index ad5bf604100d83..2c4ba1c8a2be3e 100644 --- a/regression-test/data/nereids_p0/eager_agg/eager_agg.out +++ b/regression-test/data/nereids_p0/eager_agg/eager_agg.out @@ -7,9 +7,9 @@ PhysicalResultSink --------hashAgg[GLOBAL] ----------hashAgg[LOCAL] ------------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() ---------------PhysicalOlapScan[store_sales] ---------------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +--------------PhysicalOlapScan[store_sales(ss)] +--------------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -28,9 +28,9 @@ PhysicalResultSink --------hashAgg[GLOBAL] ----------hashAgg[LOCAL] ------------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() ---------------PhysicalOlapScan[store_sales] ---------------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +--------------PhysicalOlapScan[store_sales(ss)] +--------------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -49,9 +49,9 @@ PhysicalResultSink --------hashAgg[GLOBAL] ----------hashAgg[LOCAL] ------------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() ---------------PhysicalOlapScan[store_sales] ---------------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +--------------PhysicalOlapScan[store_sales(ss)] +--------------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -68,9 +68,9 @@ PhysicalResultSink ----hashAgg[LOCAL] ------hashJoin[INNER_JOIN] hashCondition=((dt.d_date_sk = ss.ss_sold_date_sk)) otherCondition=() --------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() -----------PhysicalOlapScan[store_sales] -----------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +----------PhysicalOlapScan[store_sales(ss)] +----------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -87,11 +87,11 @@ PhysicalResultSink ----hashAgg[LOCAL] ------hashJoin[INNER_JOIN] hashCondition=((dt.d_date_sk = ss.ss_sold_date_sk)) otherCondition=() --------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() -----------PhysicalOlapScan[store_sales] +----------PhysicalOlapScan[store_sales(ss)] ----------hashAgg[GLOBAL] ------------hashAgg[LOCAL] ---------------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +--------------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -110,9 +110,9 @@ PhysicalResultSink --------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() ----------hashAgg[GLOBAL] ------------hashAgg[LOCAL] ---------------PhysicalOlapScan[store_sales] -----------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +--------------PhysicalOlapScan[store_sales(ss)] +----------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -131,9 +131,9 @@ PhysicalResultSink --------hashAgg[GLOBAL] ----------hashAgg[LOCAL] ------------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() ---------------PhysicalOlapScan[store_sales] ---------------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +--------------PhysicalOlapScan[store_sales(ss)] +--------------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -152,9 +152,9 @@ PhysicalResultSink --------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() ----------hashAgg[GLOBAL] ------------hashAgg[LOCAL] ---------------PhysicalOlapScan[store_sales] -----------PhysicalOlapScan[web_sales] ---------PhysicalOlapScan[date_dim] +--------------PhysicalOlapScan[store_sales(ss)] +----------PhysicalOlapScan[web_sales(ws)] +--------PhysicalOlapScan[date_dim(dt)] Hint log: Used: leading({ ss broadcast ws } broadcast dt ) @@ -173,9 +173,9 @@ PhysicalResultSink --------hashAgg[GLOBAL] ----------hashAgg[LOCAL] ------------hashJoin[INNER_JOIN] hashCondition=((dt.d_date_sk = ss.ss_sold_date_sk)) otherCondition=() ---------------PhysicalOlapScan[store_sales] ---------------PhysicalOlapScan[date_dim] ---------PhysicalOlapScan[web_sales] +--------------PhysicalOlapScan[store_sales(ss)] +--------------PhysicalOlapScan[date_dim(dt)] +--------PhysicalOlapScan[web_sales(ws)] Hint log: Used: leading({ ss broadcast dt } broadcast ws ) @@ -197,9 +197,9 @@ PhysicalResultSink --------hashJoin[INNER_JOIN] hashCondition=((dt.d_date_sk = ss.ss_sold_date_sk)) otherCondition=() ----------hashAgg[GLOBAL] ------------hashAgg[LOCAL] ---------------PhysicalOlapScan[store_sales] -----------PhysicalOlapScan[date_dim] ---------PhysicalOlapScan[web_sales] +--------------PhysicalOlapScan[store_sales(ss)] +----------PhysicalOlapScan[date_dim(dt)] +--------PhysicalOlapScan[web_sales(ws)] Hint log: Used: leading({ ss broadcast dt } broadcast ws ) @@ -266,11 +266,11 @@ PhysicalResultSink ------hashAgg[LOCAL] --------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() ----------hashJoin[INNER_JOIN] hashCondition=((dt.d_date_sk = ss.ss_sold_date_sk)) otherCondition=() -------------PhysicalOlapScan[store_sales] +------------PhysicalOlapScan[store_sales(ss)] ------------hashAgg[GLOBAL] --------------hashAgg[LOCAL] -----------------PhysicalOlapScan[date_dim] -----------PhysicalOlapScan[web_sales] +----------------PhysicalOlapScan[date_dim(dt)] +----------PhysicalOlapScan[web_sales(ws)] Hint log: Used: leading({ ss broadcast dt } broadcast ws ) @@ -287,9 +287,9 @@ PhysicalResultSink ----hashAgg[LOCAL] ------hashJoin[INNER_JOIN] hashCondition=((ss.ss_item_sk = ws.ws_item_sk)) otherCondition=() --------hashJoin[INNER_JOIN] hashCondition=((dt.d_date_sk = ss.ss_sold_date_sk)) otherCondition=() -----------PhysicalOlapScan[store_sales] -----------PhysicalOlapScan[date_dim] ---------PhysicalOlapScan[web_sales] +----------PhysicalOlapScan[store_sales(ss)] +----------PhysicalOlapScan[date_dim(dt)] +--------PhysicalOlapScan[web_sales(ws)] Hint log: Used: leading({ ss broadcast dt } broadcast ws ) @@ -302,8 +302,8 @@ PhysicalResultSink ----hashAgg[LOCAL] ------PhysicalUnion --------hashJoin[INNER_JOIN] hashCondition=((dt.d_date_sk = ss.ss_sold_date_sk)) otherCondition=() -----------PhysicalOlapScan[store_sales] -----------PhysicalOlapScan[date_dim] +----------PhysicalOlapScan[store_sales(ss)] +----------PhysicalOlapScan[date_dim(dt)] --------PhysicalOlapScan[date_dim] Hint log: