This is an automated email from the ASF dual-hosted git repository.

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 54f5c412205 [improvement](cascades) Fuse project pushdown into join 
reorder (#67541)
54f5c412205 is described below

commit 54f5c41220568ddcd1cf11c7ce2ba360cf1ebe73
Author: morrySnow <[email protected]>
AuthorDate: Mon Sep 7 18:16:18 2026 +0800

    [improvement](cascades) Fuse project pushdown into join reorder (#67541)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    
    `AddProjectForJoin` inserts a `LogicalProject` above every Join before
    classic Cascades exploration. Most of these Projects contain only Slots,
    but the standalone `PushDownProjectThroughSemiJoin` and
    `PushDownProjectThroughInnerOuterJoin` exploration rules still expand
    child groups and assemble temporary Plans before their predicates reject
    the match. This creates substantial CPU and allocation overhead as the
    Memo grows.
    
    This change:
    
    - extracts the existing Project normalization logic into a shared
    helper;
    - invokes that helper only when a Project-aware Join reorder rule is
    ready to produce an alternative;
    - covers inner associate/asscom/exchange, outer associate/asscom, and
    semi-join transpose paths;
    - removes the two standalone PushDown rule factories from classic
    `OTHER_REORDER_RULES`;
    - keeps the standalone rules and their `AFTER_DPHYP_REORDER_RULES`
    registration unchanged for DPHyp.
    
    Local FE-only validation:
    
    - 24 targeted FE unit tests passed, including helper edge cases, all
    Project-aware reorder families, the existing standalone rule tests, and
    a complete `AddProjectForJoin -> classic optimizer` path.
    - TPC-H 22, TPC-DS 99, and one DPHyp smoke query produced identical
    optimizer mode, physical Plan fingerprint, root cost, and limit state
    between the registered baseline and candidate.
    - The candidate removed 162 redundant Memo expressions across six
    workload queries without changing the selected Plan.
    - Across 12 STAR/DENSE × SLOT/COMPLEX × 3/8/16-table JMH pairs,
    candidate mean planning time and allocation/op were lower in every
    configuration. The geometric baseline/candidate ratios were
    1.066x/1.114x for Slot Projects and 1.698x/1.617x for Complex Projects.
    - In a 9-table JFR workload, candidate main-thread allocation fell
    16.2%, matcher allocation 34.4%, Plan-assembly allocation 39.4%, and
    `withGroupExprLogicalPropChildren` allocation 40.7%.
    
    ### Release note
    
    Reduce Nereids classic Cascades planning overhead for queries with
    multiple joins.
---
 .../org/apache/doris/nereids/rules/RuleSet.java    |   2 -
 .../exploration/join/InnerJoinLAsscomProject.java  |  17 +-
 .../join/InnerJoinLeftAssociateProject.java        |  17 +-
 .../join/InnerJoinRightAssociateProject.java       |  17 +-
 .../exploration/join/JoinExchangeBothProject.java  |  25 +-
 .../join/LogicalJoinSemiJoinTransposeProject.java  |  33 +-
 .../exploration/join/OuterJoinAssocProject.java    |  21 +-
 .../exploration/join/OuterJoinLAsscomProject.java  |  24 +-
 .../exploration/join/ProjectJoinReorderHelper.java | 192 +++++++++++
 .../join/PushDownProjectThroughInnerOuterJoin.java |  94 +-----
 .../join/PushDownProjectThroughSemiJoin.java       |  41 +--
 .../join/SemiJoinSemiJoinTransposeProject.java     |  17 +-
 .../ProjectAwareJoinReorderComplexProjectTest.java | 354 +++++++++++++++++++++
 .../join/ProjectJoinReorderHelperTest.java         | 201 ++++++++++++
 .../tpcds_sf100/noStatsRfPrune/query2.out          |  10 +-
 .../tpcds_sf100/noStatsRfPrune/query59.out         |  20 +-
 .../tpcds_sf100/no_stats_shape/query2.out          |  10 +-
 .../tpcds_sf100/no_stats_shape/query59.out         |  22 +-
 18 files changed, 894 insertions(+), 223 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java
index ff40f4e9545..185d8eb9b2c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleSet.java
@@ -148,8 +148,6 @@ public class RuleSet {
             .add(OuterJoinLAsscomProject.INSTANCE)
             .add(SemiJoinSemiJoinTransposeProject.INSTANCE)
             .add(LogicalJoinSemiJoinTransposeProject.INSTANCE)
-            .add(PushDownProjectThroughInnerOuterJoin.INSTANCE)
-            .add(PushDownProjectThroughSemiJoin.INSTANCE)
             .add(TransposeAggSemiJoinProject.INSTANCE)
             .addAll(new PushDownTopNThroughJoin().buildRules())
             .addAll(new PushDownLimitDistinctThroughJoin().buildRules())
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLAsscomProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLAsscomProject.java
index 9ff7f01b829..89e9552a72d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLAsscomProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLAsscomProject.java
@@ -32,6 +32,7 @@ import org.apache.doris.nereids.util.Utils;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -62,16 +63,20 @@ public class InnerJoinLAsscomProject extends 
OneExplorationRuleFactory {
                 innerLogicalJoin(logicalProject(innerLogicalJoin()), group())
                         .when(topJoin -> checkReorder(topJoin, 
topJoin.left().child(),
                                 enableLeftZigZag))
-                        .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint())
-                        .when(join -> join.left().isAllSlots()))
+                        .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint()))
                 .then(topProject -> {
                     LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>>, GroupPlan> topJoin
                             = topProject.child();
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedProject = 
ProjectJoinReorderHelper.normalize(topJoin.left());
+                    if (!normalizedProject.isPresent()) {
+                        return null;
+                    }
                     /* ********** init ********** */
-                    LogicalJoin<GroupPlan, GroupPlan> bottomJoin = 
topJoin.left().child();
-                    GroupPlan a = bottomJoin.left();
-                    GroupPlan b = bottomJoin.right();
-                    GroupPlan c = topJoin.right();
+                    LogicalJoin<Plan, Plan> bottomJoin = 
normalizedProject.get().child();
+                    Plan a = bottomJoin.left();
+                    Plan b = bottomJoin.right();
+                    Plan c = topJoin.right();
                     Set<ExprId> bExprIdSet = b.getOutputExprIdSet();
 
                     /* ********** split Conjuncts ********** */
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateProject.java
index e836eac3692..7deeb550705 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinLeftAssociateProject.java
@@ -31,6 +31,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -50,15 +51,19 @@ public class InnerJoinLeftAssociateProject extends 
OneExplorationRuleFactory {
     public Rule build() {
         return logicalProject(innerLogicalJoin(group(), 
logicalProject(innerLogicalJoin()))
                 .when(topJoin -> checkReorder(topJoin))
-                .whenNot(join -> join.hasDistributeHint() || 
join.right().child().hasDistributeHint())
-                .when(join -> join.right().isAllSlots()))
+                .whenNot(join -> join.hasDistributeHint() || 
join.right().child().hasDistributeHint()))
                 .then(topProject -> {
                     LogicalJoin<GroupPlan, 
LogicalProject<LogicalJoin<GroupPlan, GroupPlan>>> topJoin
                             = topProject.child();
-                    LogicalJoin<GroupPlan, GroupPlan> bottomJoin = 
topJoin.right().child();
-                    GroupPlan a = topJoin.left();
-                    GroupPlan b = bottomJoin.left();
-                    GroupPlan c = bottomJoin.right();
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedProject = 
ProjectJoinReorderHelper.normalize(topJoin.right());
+                    if (!normalizedProject.isPresent()) {
+                        return null;
+                    }
+                    LogicalJoin<Plan, Plan> bottomJoin = 
normalizedProject.get().child();
+                    Plan a = topJoin.left();
+                    Plan b = bottomJoin.left();
+                    Plan c = bottomJoin.right();
                     Set<ExprId> cExprIdSet = c.getOutputExprIdSet();
 
                     // Split condition
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateProject.java
index 841963e9a7b..6c76465444a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/InnerJoinRightAssociateProject.java
@@ -31,6 +31,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -48,15 +49,19 @@ public class InnerJoinRightAssociateProject extends 
OneExplorationRuleFactory {
     public Rule build() {
         return 
logicalProject(innerLogicalJoin(logicalProject(innerLogicalJoin()), group())
                 .when(topJoin -> checkReorder(topJoin))
-                .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint())
-                .when(join -> join.left().isAllSlots()))
+                .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint()))
                 .then(topProject -> {
                     LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>>, GroupPlan> topJoin
                             = topProject.child();
-                    LogicalJoin<GroupPlan, GroupPlan> bottomJoin = 
topJoin.left().child();
-                    GroupPlan a = bottomJoin.left();
-                    GroupPlan b = bottomJoin.right();
-                    GroupPlan c = topJoin.right();
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedProject = 
ProjectJoinReorderHelper.normalize(topJoin.left());
+                    if (!normalizedProject.isPresent()) {
+                        return null;
+                    }
+                    LogicalJoin<Plan, Plan> bottomJoin = 
normalizedProject.get().child();
+                    Plan a = bottomJoin.left();
+                    Plan b = bottomJoin.right();
+                    Plan c = topJoin.right();
                     Set<ExprId> aExprIdSet = a.getOutputExprIdSet();
 
                     // Split condition
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeBothProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeBothProject.java
index bc79a715c0b..939bc2daac9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeBothProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/JoinExchangeBothProject.java
@@ -37,6 +37,7 @@ import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -56,18 +57,24 @@ public class JoinExchangeBothProject extends 
OneExplorationRuleFactory {
     public Rule build() {
         return 
logicalProject(innerLogicalJoin(logicalProject(innerLogicalJoin()), 
logicalProject(innerLogicalJoin()))
                 .when(JoinExchangeBothProject::checkReorder)
-                .when(join -> join.left().isAllSlots() && 
join.right().isAllSlots())
                 .whenNot(join -> join.hasDistributeHint()
                         || join.left().child().hasDistributeHint() || 
join.right().child().hasDistributeHint()))
                 .then(topProject -> {
                     LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>>,
                             LogicalProject<LogicalJoin<GroupPlan, GroupPlan>>> 
topJoin = topProject.child();
-                    LogicalJoin<GroupPlan, GroupPlan> leftJoin = 
topJoin.left().child();
-                    LogicalJoin<GroupPlan, GroupPlan> rightJoin = 
topJoin.right().child();
-                    GroupPlan a = leftJoin.left();
-                    GroupPlan b = leftJoin.right();
-                    GroupPlan c = rightJoin.left();
-                    GroupPlan d = rightJoin.right();
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedLeftProject = 
ProjectJoinReorderHelper.normalize(topJoin.left());
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedRightProject = 
ProjectJoinReorderHelper.normalize(topJoin.right());
+                    if (!normalizedLeftProject.isPresent() || 
!normalizedRightProject.isPresent()) {
+                        return null;
+                    }
+                    LogicalJoin<Plan, Plan> leftJoin = 
normalizedLeftProject.get().child();
+                    LogicalJoin<Plan, Plan> rightJoin = 
normalizedRightProject.get().child();
+                    Plan a = leftJoin.left();
+                    Plan b = leftJoin.right();
+                    Plan c = rightJoin.left();
+                    Plan d = rightJoin.right();
 
                     Set<ExprId> acOutputExprIdSet = 
JoinUtils.getJoinOutputExprIdSet(a, c);
                     Set<ExprId> bdOutputExprIdSet = 
JoinUtils.getJoinOutputExprIdSet(b, d);
@@ -92,10 +99,10 @@ public class JoinExchangeBothProject extends 
OneExplorationRuleFactory {
                         return null;
                     }
 
-                    LogicalJoin<GroupPlan, GroupPlan> newLeftJoin = new 
LogicalJoin<>(JoinType.INNER_JOIN,
+                    LogicalJoin<Plan, Plan> newLeftJoin = new 
LogicalJoin<>(JoinType.INNER_JOIN,
                             newLeftJoinHashJoinConjuncts, 
newLeftJoinOtherJoinConjuncts,
                             new DistributeHint(DistributeType.NONE), a, c, 
null);
-                    LogicalJoin<GroupPlan, GroupPlan> newRightJoin = new 
LogicalJoin<>(JoinType.INNER_JOIN,
+                    LogicalJoin<Plan, Plan> newRightJoin = new 
LogicalJoin<>(JoinType.INNER_JOIN,
                             newRightJoinHashJoinConjuncts, 
newRightJoinOtherJoinConjuncts,
                             new DistributeHint(DistributeType.NONE), b, d, 
null);
                     Set<ExprId> topUsedExprIds = new HashSet<>();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/LogicalJoinSemiJoinTransposeProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/LogicalJoinSemiJoinTransposeProject.java
index 0531c6e54ac..a3f6477d3cb 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/LogicalJoinSemiJoinTransposeProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/LogicalJoinSemiJoinTransposeProject.java
@@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableList;
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -49,18 +50,22 @@ public class LogicalJoinSemiJoinTransposeProject implements 
ExplorationRuleFacto
                                 && (topJoin.getJoinType().isInnerJoin()
                                 || topJoin.getJoinType().isLeftOuterJoin())))
                         .whenNot(topJoin -> topJoin.hasDistributeHint()
-                                || topJoin.left().child().hasDistributeHint())
-                        .when(join -> join.left().isAllSlots()))
+                                || topJoin.left().child().hasDistributeHint()))
                         .then(topProject -> {
                             LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>>, GroupPlan> topJoin
                                     = topProject.child();
-                            LogicalJoin<GroupPlan, GroupPlan> bottomJoin = 
topJoin.left().child();
+                            Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                                    normalizedProject = 
ProjectJoinReorderHelper.normalize(topJoin.left());
+                            if (!normalizedProject.isPresent()) {
+                                return null;
+                            }
+                            LogicalJoin<Plan, Plan> bottomJoin = 
normalizedProject.get().child();
                             if (!JoinUtils.checkReorderPrecondition(topJoin, 
bottomJoin)) {
                                 return null;
                             }
-                            GroupPlan a = bottomJoin.left();
-                            GroupPlan b = bottomJoin.right();
-                            GroupPlan c = topJoin.right();
+                            Plan a = bottomJoin.left();
+                            Plan b = bottomJoin.right();
+                            Plan c = topJoin.right();
 
                             Set<ExprId> topUsedExprIds = new HashSet<>();
                             topProject.getProjects().forEach(expr -> 
topUsedExprIds.addAll(expr.getInputSlotExprIds()));
@@ -83,18 +88,22 @@ public class LogicalJoinSemiJoinTransposeProject implements 
ExplorationRuleFacto
                                 && (topJoin.getJoinType().isInnerJoin()
                                 || topJoin.getJoinType().isRightOuterJoin())))
                         .whenNot(topJoin -> topJoin.hasDistributeHint()
-                                || topJoin.right().child().hasDistributeHint())
-                        .when(join -> join.right().isAllSlots()))
+                                || 
topJoin.right().child().hasDistributeHint()))
                         .then(topProject -> {
                             LogicalJoin<GroupPlan, 
LogicalProject<LogicalJoin<GroupPlan, GroupPlan>>> topJoin
                                     = topProject.child();
-                            LogicalJoin<GroupPlan, GroupPlan> bottomJoin = 
topJoin.right().child();
+                            Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                                    normalizedProject = 
ProjectJoinReorderHelper.normalize(topJoin.right());
+                            if (!normalizedProject.isPresent()) {
+                                return null;
+                            }
+                            LogicalJoin<Plan, Plan> bottomJoin = 
normalizedProject.get().child();
                             if (!JoinUtils.checkReorderPrecondition(topJoin, 
bottomJoin)) {
                                 return null;
                             }
-                            GroupPlan a = topJoin.left();
-                            GroupPlan b = bottomJoin.left();
-                            GroupPlan c = bottomJoin.right();
+                            Plan a = topJoin.left();
+                            Plan b = bottomJoin.left();
+                            Plan c = bottomJoin.right();
 
                             Set<ExprId> topUsedExprIds = new HashSet<>();
                             topProject.getProjects().forEach(expr -> 
topUsedExprIds.addAll(expr.getInputSlotExprIds()));
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
index 0dcb3b8d344..6ad9cf6a861 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinAssocProject.java
@@ -38,6 +38,7 @@ import org.apache.doris.nereids.util.Utils;
 import com.google.common.collect.ImmutableSet;
 
 import java.util.HashSet;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 
@@ -66,19 +67,25 @@ public class OuterJoinAssocProject extends 
OneExplorationRuleFactory {
                 .when(join -> VALID_TYPE_PAIR_SET.contains(
                         Pair.of(join.left().child().getJoinType(), 
join.getJoinType())))
                 .when(topJoin -> OuterJoinLAsscomProject.checkReorder(topJoin, 
topJoin.left().child()))
-                .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint())
-                .when(join -> checkCondition(join, 
join.left().child().left().getOutputSet()))
-                .when(join -> join.left().isAllSlots()))
+                .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint()))
                 .thenApply(ctx -> {
                     
LogicalProject<LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, GroupPlan>>, 
GroupPlan>> topProject
                             = ctx.root;
                     LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>>, GroupPlan> topJoin
                             = topProject.child();
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedProject = 
ProjectJoinReorderHelper.normalize(topJoin.left());
+                    if (!normalizedProject.isPresent()) {
+                        return null;
+                    }
                     /* ********** init ********** */
-                    LogicalJoin<GroupPlan, GroupPlan> bottomJoin = 
topJoin.left().child();
-                    GroupPlan a = bottomJoin.left();
-                    GroupPlan b = bottomJoin.right();
-                    GroupPlan c = topJoin.right();
+                    LogicalJoin<Plan, Plan> bottomJoin = 
normalizedProject.get().child();
+                    Plan a = bottomJoin.left();
+                    Plan b = bottomJoin.right();
+                    Plan c = topJoin.right();
+                    if (!checkCondition(topJoin, a.getOutputSet())) {
+                        return null;
+                    }
 
                     /*
                      * Paper `On the Correct and Complete Enumeration of the 
Core Search Space`.
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
index 24fa7722970..e166e32e098 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
@@ -34,6 +34,7 @@ import org.apache.doris.nereids.util.Utils;
 import com.google.common.collect.ImmutableSet;
 
 import java.util.HashSet;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 
@@ -65,18 +66,23 @@ public class OuterJoinLAsscomProject extends 
OneExplorationRuleFactory {
                 .when(join -> 
OuterJoinLAsscomProject.VALID_TYPE_PAIR_SET.contains(
                         Pair.of(join.left().child().getJoinType(), 
join.getJoinType())))
                 .when(topJoin -> OuterJoinLAsscomProject.checkReorder(topJoin, 
topJoin.left().child()))
-                .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint())
-                .when(topJoin -> 
OuterJoinLAsscomProject.checkCondition(topJoin,
-                        topJoin.left().child().right().getOutputExprIdSet()))
-                .when(join -> join.left().isAllSlots()))
+                .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint()))
                 .then(topProject -> {
                     LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>>, GroupPlan> topJoin
                             = topProject.child();
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedProject = 
ProjectJoinReorderHelper.normalize(topJoin.left());
+                    if (!normalizedProject.isPresent()) {
+                        return null;
+                    }
                     /* ********** init ********** */
-                    LogicalJoin<GroupPlan, GroupPlan> bottomJoin = 
topJoin.left().child();
-                    GroupPlan a = bottomJoin.left();
-                    GroupPlan b = bottomJoin.right();
-                    GroupPlan c = topJoin.right();
+                    LogicalJoin<Plan, Plan> bottomJoin = 
normalizedProject.get().child();
+                    Plan a = bottomJoin.left();
+                    Plan b = bottomJoin.right();
+                    Plan c = topJoin.right();
+                    if (!OuterJoinLAsscomProject.checkCondition(topJoin, 
b.getOutputExprIdSet())) {
+                        return null;
+                    }
 
                     /* ********** new Plan ********** */
                     LogicalJoin newBottomJoin = 
topJoin.withChildrenNoContext(a, c, null);
@@ -122,7 +128,7 @@ public class OuterJoinLAsscomProject extends 
OneExplorationRuleFactory {
      * check join reorder masks.
      */
     public static boolean checkReorder(LogicalJoin<? extends Plan, GroupPlan> 
topJoin,
-            LogicalJoin<GroupPlan, GroupPlan> bottomJoin) {
+            LogicalJoin<? extends Plan, ? extends Plan> bottomJoin) {
         // hasCommute will cause to lack of OuterJoinAssocRule:Left
         return !topJoin.getJoinReorderContext().hasLAsscom()
                 && !topJoin.getJoinReorderContext().hasLeftAssociate()
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/ProjectJoinReorderHelper.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/ProjectJoinReorderHelper.java
new file mode 100644
index 00000000000..28113b32617
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/ProjectJoinReorderHelper.java
@@ -0,0 +1,192 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.exploration.join;
+
+import org.apache.doris.nereids.rules.exploration.CBOUtils;
+import org.apache.doris.nereids.trees.expressions.ExprId;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Normalize a Project over Join only when a Project-aware join reorder rule 
produces an alternative. */
+final class ProjectJoinReorderHelper {
+    private ProjectJoinReorderHelper() {
+    }
+
+    /**
+     * Keep a slot-only Project unchanged, or push single-side complex 
expressions below its Join.
+     *
+     * <p>The returned Project is slot-only and has the same output as the 
input Project. Empty means that
+     * the Project cannot be moved without changing semantics.</p>
+     */
+    static Optional<LogicalProject<LogicalJoin<Plan, Plan>>> 
normalize(LogicalProject<?> project) {
+        if (project.isAllSlots()) {
+            return Optional.of(castProject(project));
+        }
+
+        LogicalJoin<Plan, Plan> join = childJoin(project);
+        JoinType joinType = join.getJoinType();
+        if (joinType.isLeftSemiOrAntiJoin()) {
+            if (join.isMarkJoin() || projectBothJoinSides(project)) {
+                return Optional.empty();
+            }
+            return Optional.of(pushDownLeftSemiProject(project, join));
+        }
+        if (joinType.isInnerJoin() || joinType.isOuterJoin()
+                || joinType.isAsofInnerJoin() || joinType.isAsofOuterJoin()) {
+            return pushDownInnerOuterProject(project, join);
+        }
+        return Optional.empty();
+    }
+
+    private static boolean projectBothJoinSides(LogicalProject<?> project) {
+        LogicalJoin<Plan, Plan> join = childJoin(project);
+        Set<Slot> projectOutput = project.getOutputSet();
+        boolean containLeft = 
join.left().getOutput().stream().anyMatch(projectOutput::contains);
+        boolean containRight = 
join.right().getOutput().stream().anyMatch(projectOutput::contains);
+        return containLeft && containRight;
+    }
+
+    private static LogicalProject<LogicalJoin<Plan, Plan>> 
pushDownLeftSemiProject(
+            LogicalProject<?> project, LogicalJoin<Plan, Plan> join) {
+        Set<Slot> conditionLeftSlots = CBOUtils.joinChildConditionSlots(join, 
true);
+        List<NamedExpression> newProjects = new 
ArrayList<>(project.getProjects());
+        Set<Slot> projectUsedSlots = project.getProjects().stream()
+                .map(NamedExpression::toSlot)
+                .collect(Collectors.toSet());
+        conditionLeftSlots.stream()
+                .filter(slot -> !projectUsedSlots.contains(slot))
+                .forEach(newProjects::add);
+
+        Plan newLeft = new LogicalProject<>(newProjects, join.left());
+        LogicalJoin<Plan, Plan> newJoin = 
join.withChildren(ImmutableList.of(newLeft, join.right()));
+        return new LogicalProject<>(ImmutableList.copyOf(project.getOutput()), 
newJoin);
+    }
+
+    private static Optional<LogicalProject<LogicalJoin<Plan, Plan>>> 
pushDownInnerOuterProject(
+            LogicalProject<?> project, LogicalJoin<Plan, Plan> join) {
+        Set<ExprId> leftOutputExprIds = join.left().getOutputExprIdSet();
+        Set<ExprId> rightOutputExprIds = join.right().getOutputExprIdSet();
+
+        boolean containsHyperEdge = 
project.getProjects().stream().anyMatch(expression -> {
+            Set<ExprId> inputExprIds = expression.getInputSlotExprIds();
+            return !leftOutputExprIds.containsAll(inputExprIds)
+                    && !rightOutputExprIds.containsAll(inputExprIds);
+        });
+        if (containsHyperEdge) {
+            return Optional.empty();
+        }
+
+        List<NamedExpression> projects = adjustProjectsNullable(project, join);
+        List<NamedExpression> leftProjects = new ArrayList<>();
+        List<NamedExpression> rightProjects = new ArrayList<>();
+        for (NamedExpression expression : projects) {
+            if 
(leftOutputExprIds.containsAll(expression.getInputSlotExprIds())) {
+                leftProjects.add(expression);
+            } else {
+                rightProjects.add(expression);
+            }
+        }
+
+        boolean leftContainsComplexExpression = leftProjects.stream()
+                .anyMatch(expression -> !(expression instanceof Slot));
+        boolean rightContainsComplexExpression = rightProjects.stream()
+                .anyMatch(expression -> !(expression instanceof Slot));
+        // JoinCommute supplies the orientation in which a movable complex 
expression is on the left.
+        if (!leftContainsComplexExpression) {
+            return Optional.empty();
+        }
+        if ((join.getJoinType().isRightSideNullable() && 
rightContainsComplexExpression)
+                || (join.getJoinType().isLeftSideNullable() && 
leftContainsComplexExpression)) {
+            return Optional.empty();
+        }
+
+        Builder<NamedExpression> newLeftProjects = 
ImmutableList.<NamedExpression>builder()
+                .addAll(leftProjects);
+        Set<Slot> leftConditionSlots = CBOUtils.joinChildConditionSlots(join, 
true);
+        Set<Slot> leftProjectSlots = leftProjects.stream()
+                .map(NamedExpression::toSlot)
+                .collect(Collectors.toSet());
+        leftConditionSlots.stream()
+                .filter(slot -> !leftProjectSlots.contains(slot))
+                .forEach(newLeftProjects::add);
+        Plan newLeft = new LogicalProject<>(newLeftProjects.build(), 
join.left());
+
+        Plan newRight = join.right();
+        if (rightContainsComplexExpression) {
+            Builder<NamedExpression> newRightProjects = 
ImmutableList.<NamedExpression>builder()
+                    .addAll(rightProjects);
+            Set<Slot> rightConditionSlots = 
CBOUtils.joinChildConditionSlots(join, false);
+            Set<Slot> rightProjectSlots = rightProjects.stream()
+                    .map(NamedExpression::toSlot)
+                    .collect(Collectors.toSet());
+            rightConditionSlots.stream()
+                    .filter(slot -> !rightProjectSlots.contains(slot))
+                    .forEach(newRightProjects::add);
+            newRight = new LogicalProject<>(newRightProjects.build(), 
join.right());
+        }
+
+        LogicalJoin<Plan, Plan> newJoin = 
join.withChildren(ImmutableList.of(newLeft, newRight));
+        return Optional.of(new 
LogicalProject<>(ImmutableList.copyOf(project.getOutput()), newJoin));
+    }
+
+    private static List<NamedExpression> adjustProjectsNullable(
+            LogicalProject<?> project, LogicalJoin<Plan, Plan> join) {
+        if (join.getJoinType().isInnerJoin() || 
join.getJoinType().isAsofInnerJoin()) {
+            return project.getProjects();
+        }
+
+        Map<Slot, Slot> childSlots = new HashMap<>();
+        join.left().getOutputSet().forEach(slot -> childSlots.put(slot, slot));
+        join.right().getOutputSet().forEach(slot -> childSlots.put(slot, 
slot));
+        join.getOutputSet().forEach(slot -> {
+            if (childSlots.containsKey(slot)) {
+                childSlots.put(slot, childSlots.get(slot));
+            }
+        });
+        return project.getProjects().stream()
+                .map(expression -> expression.rewriteUp(child ->
+                        child instanceof Slot ? childSlots.get((Slot) child) : 
child))
+                .map(NamedExpression.class::cast)
+                .collect(Collectors.toList());
+    }
+
+    @SuppressWarnings("unchecked")
+    private static LogicalJoin<Plan, Plan> childJoin(LogicalProject<?> 
project) {
+        return (LogicalJoin<Plan, Plan>) project.child();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static LogicalProject<LogicalJoin<Plan, Plan>> 
castProject(LogicalProject<?> project) {
+        return (LogicalProject<LogicalJoin<Plan, Plan>>) project;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughInnerOuterJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughInnerOuterJoin.java
index 905af662a2d..8a9c44015d5 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughInnerOuterJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughInnerOuterJoin.java
@@ -19,25 +19,15 @@ package org.apache.doris.nereids.rules.exploration.join;
 
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
-import org.apache.doris.nereids.rules.exploration.CBOUtils;
 import org.apache.doris.nereids.rules.exploration.ExplorationRuleFactory;
-import org.apache.doris.nereids.trees.expressions.ExprId;
-import org.apache.doris.nereids.trees.expressions.NamedExpression;
-import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.GroupPlan;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Rule for pushdown project through inner/outer join
@@ -66,7 +56,7 @@ public class PushDownProjectThroughInnerOuterJoin implements 
ExplorationRuleFact
                         .whenNot(j -> j.left().child().hasDistributeHint())
                         .then(topJoin -> {
                             LogicalProject<LogicalJoin<GroupPlan, GroupPlan>> 
project = topJoin.left();
-                            Plan newLeft = pushdownProject(project);
+                            Plan newLeft = 
ProjectJoinReorderHelper.normalize(project).orElse(null);
                             if (newLeft == null) {
                                 return null;
                             }
@@ -82,7 +72,7 @@ public class PushDownProjectThroughInnerOuterJoin implements 
ExplorationRuleFact
                         .whenNot(j -> j.right().child().hasDistributeHint())
                         .then(topJoin -> {
                             LogicalProject<LogicalJoin<GroupPlan, GroupPlan>> 
project = topJoin.right();
-                            Plan newRight = pushdownProject(project);
+                            Plan newRight = 
ProjectJoinReorderHelper.normalize(project).orElse(null);
                             if (newRight == null) {
                                 return null;
                             }
@@ -90,84 +80,4 @@ public class PushDownProjectThroughInnerOuterJoin implements 
ExplorationRuleFact
                         
}).toRule(RuleType.PUSH_DOWN_PROJECT_THROUGH_INNER_OUTER_JOIN_RIGHT)
         );
     }
-
-    private Plan pushdownProject(LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>> project) {
-        LogicalJoin<GroupPlan, GroupPlan> join = project.child();
-        Set<ExprId> aOutputExprIdSet = join.left().getOutputExprIdSet();
-        Set<ExprId> bOutputExprIdSet = join.right().getOutputExprIdSet();
-
-        // reject hyper edge in Project.
-        if (!project.getProjects().stream().allMatch(expr -> {
-            Set<ExprId> inputSlotExprIds = expr.getInputSlotExprIds();
-            return aOutputExprIdSet.containsAll(inputSlotExprIds)
-                    || bOutputExprIdSet.containsAll(inputSlotExprIds);
-        })) {
-            return null;
-        }
-
-        List<NamedExpression> aProjects = new ArrayList<>();
-        List<NamedExpression> bProjects = new ArrayList<>();
-        List<NamedExpression> projects;
-        if (join.getJoinType().isInnerJoin() || 
join.getJoinType().isAsofInnerJoin()) {
-            projects = project.getProjects();
-        } else {
-            Map<Slot, Slot> childrenSlots = new HashMap<>();
-            join.left().getOutputSet().forEach(slot -> childrenSlots.put(slot, 
slot));
-            join.right().getOutputSet().forEach(slot -> 
childrenSlots.put(slot, slot));
-            join.getOutputSet().forEach(slot -> {
-                if (childrenSlots.containsKey(slot)) {
-                    childrenSlots.put(slot, childrenSlots.get(slot));
-                }
-            });
-
-            projects = project.getProjects().stream().map(expr -> 
expr.rewriteUp(e ->
-                    e instanceof Slot ? childrenSlots.get((Slot) e) : e
-            )).map(e -> (NamedExpression) e).collect(Collectors.toList());
-        }
-        for (NamedExpression namedExpression : projects) {
-            Set<ExprId> usedExprIds = namedExpression.getInputSlotExprIds();
-            if (aOutputExprIdSet.containsAll(usedExprIds)) {
-                aProjects.add(namedExpression);
-            } else {
-                bProjects.add(namedExpression);
-            }
-        }
-
-        boolean leftContains = aProjects.stream().anyMatch(e -> !(e instanceof 
Slot));
-        boolean rightContains = bProjects.stream().anyMatch(e -> !(e 
instanceof Slot));
-        // due to JoinCommute, we don't need to consider just right contains.
-        if (!leftContains) {
-            return null;
-        }
-        // we could not push nullable side project
-        if (((join.getJoinType().isLeftOuterJoin() || 
join.getJoinType().isAsofLeftOuterJoin()
-                || join.getJoinType().isFullOuterJoin()) && rightContains)
-                || ((join.getJoinType().isRightOuterJoin() || 
join.getJoinType().isAsofRightOuterJoin()
-                || join.getJoinType().isFullOuterJoin()) && leftContains)) {
-            return null;
-        }
-
-        Builder<NamedExpression> newAProject = 
ImmutableList.<NamedExpression>builder().addAll(aProjects);
-        Set<Slot> aConditionSlots = CBOUtils.joinChildConditionSlots(join, 
true);
-        Set<Slot> aProjectSlots = 
aProjects.stream().map(NamedExpression::toSlot)
-                .collect(Collectors.toSet());
-        aConditionSlots.stream().filter(slot -> 
!aProjectSlots.contains(slot)).forEach(newAProject::add);
-        Plan newLeft = new LogicalProject<>(newAProject.build(), join.left());
-
-        if (!rightContains) {
-            Plan newJoin = join.withChildren(newLeft, join.right());
-            return new 
LogicalProject<>(ImmutableList.copyOf(project.getOutput()), newJoin);
-        }
-
-        Builder<NamedExpression> newBProject = 
ImmutableList.<NamedExpression>builder().addAll(bProjects);
-        Set<Slot> bConditionSlots = CBOUtils.joinChildConditionSlots(join, 
false);
-        Set<Slot> bProjectSlots = 
bProjects.stream().map(NamedExpression::toSlot)
-                .collect(Collectors.toSet());
-        bConditionSlots.stream().filter(slot -> 
!bProjectSlots.contains(slot)).forEach(newBProject::add);
-        Plan newRight = new LogicalProject<>(newBProject.build(), 
join.right());
-
-        Plan newJoin = join.withChildren(newLeft, newRight);
-        return new LogicalProject<>(ImmutableList.copyOf(project.getOutput()), 
newJoin);
-    }
-
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughSemiJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughSemiJoin.java
index 11efc82fc21..74d8befe3fa 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughSemiJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/PushDownProjectThroughSemiJoin.java
@@ -19,10 +19,7 @@ package org.apache.doris.nereids.rules.exploration.join;
 
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
-import org.apache.doris.nereids.rules.exploration.CBOUtils;
 import org.apache.doris.nereids.rules.exploration.ExplorationRuleFactory;
-import org.apache.doris.nereids.trees.expressions.NamedExpression;
-import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.GroupPlan;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
@@ -30,10 +27,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 
 import com.google.common.collect.ImmutableList;
 
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Rule for pushdown project through left-semi/anti join
@@ -62,10 +56,10 @@ public class PushDownProjectThroughSemiJoin implements 
ExplorationRuleFactory {
                     .whenNot(j -> j.left().child().hasDistributeHint())
                     .then(topJoin -> {
                         LogicalProject<LogicalJoin<GroupPlan, GroupPlan>> 
project = topJoin.left();
-                        if (projectBothJoinSide(project)) {
+                        Plan newLeft = 
ProjectJoinReorderHelper.normalize(project).orElse(null);
+                        if (newLeft == null) {
                             return null;
                         }
-                        Plan newLeft = pushdownProject(project);
                         return topJoin.withChildren(newLeft, topJoin.right());
                     
}).toRule(RuleType.PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN_LEFT),
 
@@ -76,39 +70,12 @@ public class PushDownProjectThroughSemiJoin implements 
ExplorationRuleFactory {
                     .whenNot(j -> j.right().child().hasDistributeHint())
                     .then(topJoin -> {
                         LogicalProject<LogicalJoin<GroupPlan, GroupPlan>> 
project = topJoin.right();
-                        if (projectBothJoinSide(project)) {
+                        Plan newRight = 
ProjectJoinReorderHelper.normalize(project).orElse(null);
+                        if (newRight == null) {
                             return null;
                         }
-                        Plan newRight = pushdownProject(project);
                         return topJoin.withChildren(topJoin.left(), newRight);
                     
}).toRule(RuleType.PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN_RIGHT)
                 );
     }
-
-    private boolean projectBothJoinSide(LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>> project) {
-        // if project contains both side of join, it can't be pushed.
-        // such as:
-        //  Project(l, null as r)
-        //  ------ L(l) left anti join R(r)
-        LogicalJoin<?, ?> join = project.child();
-        Set<Slot> projectOutput = project.getOutputSet();
-        boolean containLeft = 
join.left().getOutput().stream().anyMatch(projectOutput::contains);
-        boolean containRight = 
join.right().getOutput().stream().anyMatch(projectOutput::contains);
-        return containRight && containLeft;
-    }
-
-    private Plan pushdownProject(LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>> project) {
-        LogicalJoin<GroupPlan, GroupPlan> join = project.child();
-        Set<Slot> conditionLeftSlots = CBOUtils.joinChildConditionSlots(join, 
true);
-
-        List<NamedExpression> newProject = new 
ArrayList<>(project.getProjects());
-        Set<Slot> projectUsedSlots = 
project.getProjects().stream().map(NamedExpression::toSlot)
-                .collect(Collectors.toSet());
-        conditionLeftSlots.stream().filter(slot -> 
!projectUsedSlots.contains(slot))
-                .forEach(newProject::add);
-        Plan newLeft = new LogicalProject<>(newProject, join.left());
-
-        Plan newJoin = join.withChildren(newLeft, join.right());
-        return new LogicalProject<>(ImmutableList.copyOf(project.getOutput()), 
newJoin);
-    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/SemiJoinSemiJoinTransposeProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/SemiJoinSemiJoinTransposeProject.java
index 8d1d67af5c0..18c79ce6b20 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/SemiJoinSemiJoinTransposeProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/SemiJoinSemiJoinTransposeProject.java
@@ -33,6 +33,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import com.google.common.collect.ImmutableSet;
 
 import java.util.HashSet;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -68,7 +69,6 @@ public class SemiJoinSemiJoinTransposeProject extends 
OneExplorationRuleFactory
                 .when(this::typeChecker)
                 .when(topSemi -> InnerJoinLAsscomProject.checkReorder(topSemi, 
topSemi.left().child(), false))
                 .whenNot(join -> join.hasDistributeHint() || 
join.left().child().hasDistributeHint())
-                .when(join -> join.left().isAllSlots())
                 // the transpose swaps the bottom semi join to the top, so the 
mark slot
                 // produced by the bottom mark join would be produced by the 
new top semi
                 // join. if the top semi join references the mark slot in its 
conjuncts,
@@ -80,11 +80,16 @@ public class SemiJoinSemiJoinTransposeProject extends 
OneExplorationRuleFactory
                 .then(topProject -> {
                     LogicalJoin<LogicalProject<LogicalJoin<GroupPlan, 
GroupPlan>>, GroupPlan> topSemi
                             = topProject.child();
-                    LogicalJoin<GroupPlan, GroupPlan> bottomSemi = 
topSemi.left().child();
-                    LogicalProject<LogicalJoin<GroupPlan, GroupPlan>> 
abProject = topSemi.left();
-                    GroupPlan a = bottomSemi.left();
-                    GroupPlan b = bottomSemi.right();
-                    GroupPlan c = topSemi.right();
+                    Optional<LogicalProject<LogicalJoin<Plan, Plan>>>
+                            normalizedProject = 
ProjectJoinReorderHelper.normalize(topSemi.left());
+                    if (!normalizedProject.isPresent()) {
+                        return null;
+                    }
+                    LogicalJoin<Plan, Plan> bottomSemi = 
normalizedProject.get().child();
+                    LogicalProject<LogicalJoin<Plan, Plan>> abProject = 
normalizedProject.get();
+                    Plan a = bottomSemi.left();
+                    Plan b = bottomSemi.right();
+                    Plan c = topSemi.right();
                     Set<ExprId> aOutputExprIdSet = a.getOutputExprIdSet();
                     // if bottom semi join is mark join, we need remove the 
mark join slot creating by bottom semi join
                     // from the project list before swapping the bottom semi 
to top semi
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/ProjectAwareJoinReorderComplexProjectTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/ProjectAwareJoinReorderComplexProjectTest.java
new file mode 100644
index 00000000000..e124f9e3b93
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/ProjectAwareJoinReorderComplexProjectTest.java
@@ -0,0 +1,354 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.exploration.join;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.nereids.rules.Rule;
+import org.apache.doris.nereids.rules.rewrite.AddProjectForJoin;
+import org.apache.doris.nereids.rules.rewrite.MergeProjectable;
+import org.apache.doris.nereids.trees.expressions.Add;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.util.LogicalPlanBuilder;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.nereids.util.PlanConstructor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.SessionVariable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+class ProjectAwareJoinReorderComplexProjectTest {
+
+    @Test
+    void completeClassicOptimizerReordersPlanAfterAddProjectForJoin() {
+        LogicalPlan barePlan = buildBarePlan(4);
+        List<String> expectedOutput = outputSignature(barePlan);
+        ConnectContext connectContext = MemoTestUtils.createConnectContext();
+        SessionVariable sessionVariable = connectContext.getSessionVariable();
+        sessionVariable.enableDPHypOptimizer = false;
+        sessionVariable.setMaxTableCountUseCascadesJoinReorder(64);
+        sessionVariable.joinReorderTimeLimit = 600_000;
+
+        PlanChecker checker = PlanChecker.from(connectContext, barePlan)
+                .customRewrite(new AddProjectForJoin())
+                .applyTopDown(new MergeProjectable());
+        Plan rewrittenPlan = checker.getCascadesContext().getRewritePlan();
+
+        long joinCount = rewrittenPlan.collect(plan -> plan instanceof 
LogicalJoin).size();
+        long projectOnJoinCount = rewrittenPlan.collect(plan -> plan 
instanceof LogicalProject
+                && plan.child(0) instanceof LogicalJoin).size();
+        Assertions.assertEquals(3, joinCount);
+        Assertions.assertEquals(joinCount, projectOnJoinCount);
+        Assertions.assertEquals(expectedOutput, 
outputSignature(rewrittenPlan));
+
+        checker.optimize();
+        Assertions.assertEquals(expectedOutput, 
outputSignature(checker.getBestPlanTree()));
+        Set<String> joinTopologies = checker.getAllPlan().stream()
+                .map(ProjectAwareJoinReorderComplexProjectTest::joinTopology)
+                .filter(topology -> topology.startsWith("("))
+                .collect(Collectors.toSet());
+        Assertions.assertTrue(joinTopologies.size() > 1, 
joinTopologies::toString);
+    }
+
+    @Test
+    void innerLAsscomMovesComplexExpressionWithItsInput() {
+        LogicalOlapScan a = scan(0, "a");
+        LogicalOlapScan b = scan(1, "b");
+        LogicalOlapScan c = scan(2, "c");
+        Alias alias = alias(a, "a_alias");
+        LogicalPlan ab = project(join(a, b, JoinType.INNER_JOIN, 0, 0),
+                alias, a.getOutput().get(1), b.getOutput().get(0), 
b.getOutput().get(1));
+        LogicalPlan original = projectAll(join(ab, c, JoinType.INNER_JOIN, 0, 
0));
+
+        Plan reordered = 
applyAndFind(InnerJoinLAsscomProject.INSTANCE.build(), original,
+                plan -> hasJoin(plan, JoinType.INNER_JOIN, names("a"), 
names("c"))
+                        && hasJoin(plan, JoinType.INNER_JOIN, names("a", "c"), 
names("b")));
+
+        assertSemanticsPreserved(original, reordered);
+        assertAliasPlacedOnScan(reordered, alias, "a");
+    }
+
+    @Test
+    void innerAssociatesInBothDirectionsWithComplexExpression() {
+        LogicalOlapScan a = scan(0, "a");
+        LogicalOlapScan b = scan(1, "b");
+        LogicalOlapScan c = scan(2, "c");
+        Alias rightAssociateAlias = alias(a, "a_alias");
+        LogicalPlan ab = project(join(a, b, JoinType.INNER_JOIN, 0, 0),
+                rightAssociateAlias, a.getOutput().get(1), 
b.getOutput().get(0), b.getOutput().get(1));
+        LogicalPlan rightAssociateOriginal = projectAll(join(ab, c, 
JoinType.INNER_JOIN, 2, 0));
+
+        Plan rightAssociated = 
applyAndFind(InnerJoinRightAssociateProject.INSTANCE.build(),
+                rightAssociateOriginal,
+                plan -> hasJoin(plan, JoinType.INNER_JOIN, names("b"), 
names("c"))
+                        && hasJoin(plan, JoinType.INNER_JOIN, names("a"), 
names("b", "c")));
+        assertSemanticsPreserved(rightAssociateOriginal, rightAssociated);
+        assertAliasPlacedOnScan(rightAssociated, rightAssociateAlias, "a");
+
+        Alias leftAssociateAlias = alias(b, "b_alias");
+        LogicalPlan bc = project(join(b, c, JoinType.INNER_JOIN, 0, 0),
+                leftAssociateAlias, b.getOutput().get(0), b.getOutput().get(1),
+                c.getOutput().get(0), c.getOutput().get(1));
+        LogicalPlan leftAssociateOriginal = projectAll(join(a, bc, 
JoinType.INNER_JOIN, 0, 1));
+
+        Plan leftAssociated = 
applyAndFind(InnerJoinLeftAssociateProject.INSTANCE.build(),
+                leftAssociateOriginal,
+                plan -> hasJoin(plan, JoinType.INNER_JOIN, names("a"), 
names("b"))
+                        && hasJoin(plan, JoinType.INNER_JOIN, names("a", "b"), 
names("c")));
+        assertSemanticsPreserved(leftAssociateOriginal, leftAssociated);
+        assertAliasPlacedOnScan(leftAssociated, leftAssociateAlias, "b");
+    }
+
+    @Test
+    void exchangePushesIndependentComplexExpressionsOnBothBranches() {
+        LogicalOlapScan a = scan(0, "a");
+        LogicalOlapScan b = scan(1, "b");
+        LogicalOlapScan c = scan(2, "c");
+        LogicalOlapScan d = scan(3, "d");
+        Alias aAlias = alias(a, "a_alias");
+        Alias cAlias = alias(c, "c_alias");
+        LogicalPlan ab = project(join(a, b, JoinType.INNER_JOIN, 0, 0),
+                aAlias, a.getOutput().get(0), a.getOutput().get(1),
+                b.getOutput().get(0), b.getOutput().get(1));
+        LogicalPlan cd = project(join(c, d, JoinType.INNER_JOIN, 0, 0),
+                cAlias, c.getOutput().get(0), c.getOutput().get(1),
+                d.getOutput().get(0), d.getOutput().get(1));
+        LogicalPlan original = projectAll(new LogicalPlanBuilder(ab)
+                .join(cd, JoinType.INNER_JOIN, ImmutableList.of(Pair.of(1, 1), 
Pair.of(3, 3)))
+                .build());
+
+        Plan reordered = 
applyAndFind(JoinExchangeBothProject.INSTANCE.build(), original,
+                plan -> hasJoin(plan, JoinType.INNER_JOIN, names("a"), 
names("c"))
+                        && hasJoin(plan, JoinType.INNER_JOIN, names("b"), 
names("d")));
+
+        assertSemanticsPreserved(original, reordered);
+        assertAliasPlacedOnScan(reordered, aAlias, "a");
+        assertAliasPlacedOnScan(reordered, cAlias, "c");
+    }
+
+    @Test
+    void transposeLogicalJoinAndSemiJoinWithComplexExpression() {
+        LogicalOlapScan a = scan(0, "a");
+        LogicalOlapScan b = scan(1, "b");
+        LogicalOlapScan c = scan(2, "c");
+        Alias alias = alias(a, "a_alias");
+        LogicalPlan semi = project(join(a, b, JoinType.LEFT_SEMI_JOIN, 0, 0),
+                alias, a.getOutput().get(0), a.getOutput().get(1));
+        LogicalPlan original = projectAll(join(semi, c, JoinType.INNER_JOIN, 
0, 0));
+
+        Plan reordered = applyAndFind(
+                
LogicalJoinSemiJoinTransposeProject.INSTANCE.buildRules().get(0), original,
+                plan -> hasJoin(plan, JoinType.INNER_JOIN, names("a"), 
names("c"))
+                        && hasJoin(plan, JoinType.LEFT_SEMI_JOIN, names("a", 
"c"), names("b")));
+
+        assertSemanticsPreserved(original, reordered);
+        assertAliasPlacedOnScan(reordered, alias, "a");
+    }
+
+    @Test
+    void transposeTwoSemiJoinsWithComplexExpression() {
+        LogicalOlapScan a = scan(0, "a");
+        LogicalOlapScan b = scan(1, "b");
+        LogicalOlapScan c = scan(2, "c");
+        Alias alias = alias(a, "a_alias");
+        LogicalPlan bottomSemi = project(join(a, b, JoinType.LEFT_SEMI_JOIN, 
0, 0),
+                alias, a.getOutput().get(0), a.getOutput().get(1));
+        LogicalPlan original = projectAll(join(bottomSemi, c, 
JoinType.LEFT_SEMI_JOIN, 0, 0));
+
+        Plan reordered = 
applyAndFind(SemiJoinSemiJoinTransposeProject.INSTANCE.build(), original,
+                plan -> hasJoin(plan, JoinType.LEFT_SEMI_JOIN, names("a"), 
names("c"))
+                        && hasJoin(plan, JoinType.LEFT_SEMI_JOIN, names("a", 
"c"), names("b")));
+
+        assertSemanticsPreserved(original, reordered);
+        assertAliasPlacedOnScan(reordered, alias, "a");
+    }
+
+    @Test
+    void outerJoinReordersKeepComplexExpressionOnNonNullableInput() {
+        LogicalOlapScan a = scan(0, "a");
+        LogicalOlapScan b = scan(1, "b");
+        LogicalOlapScan c = scan(2, "c");
+        Alias lAsscomAlias = alias(a, "a_lasscom_alias");
+        LogicalPlan abForLAsscom = project(join(a, b, JoinType.INNER_JOIN, 0, 
0),
+                lAsscomAlias, a.getOutput().get(0), a.getOutput().get(1),
+                b.getOutput().get(0), b.getOutput().get(1));
+        LogicalPlan lAsscomOriginal = projectAll(join(abForLAsscom, c, 
JoinType.LEFT_OUTER_JOIN, 0, 0));
+
+        Plan lAsscom = applyAndFind(OuterJoinLAsscomProject.INSTANCE.build(), 
lAsscomOriginal,
+                plan -> hasJoin(plan, JoinType.LEFT_OUTER_JOIN, names("a"), 
names("c"))
+                        && hasJoin(plan, JoinType.INNER_JOIN, names("a", "c"), 
names("b")));
+        assertSemanticsPreserved(lAsscomOriginal, lAsscom);
+        assertAliasPlacedOnScan(lAsscom, lAsscomAlias, "a");
+
+        Alias assocAlias = alias(a, "a_assoc_alias");
+        LogicalPlan abForAssoc = project(join(a, b, JoinType.INNER_JOIN, 0, 0),
+                assocAlias, a.getOutput().get(0), a.getOutput().get(1),
+                b.getOutput().get(0), b.getOutput().get(1));
+        LogicalPlan assocOriginal = projectAll(join(abForAssoc, c, 
JoinType.LEFT_OUTER_JOIN, 3, 0));
+
+        Plan assoc = applyAndFind(OuterJoinAssocProject.INSTANCE.build(), 
assocOriginal,
+                plan -> hasJoin(plan, JoinType.LEFT_OUTER_JOIN, names("b"), 
names("c"))
+                        && hasJoin(plan, JoinType.INNER_JOIN, names("a"), 
names("b", "c")));
+        assertSemanticsPreserved(assocOriginal, assoc);
+        assertAliasPlacedOnScan(assoc, assocAlias, "a");
+    }
+
+    private static Plan applyAndFind(Rule rule, LogicalPlan original, 
Predicate<Plan> predicate) {
+        List<Plan> plans = 
PlanChecker.from(MemoTestUtils.createConnectContext(), original)
+                .applyExploration(rule)
+                .getAllPlan();
+        return plans.stream()
+                .filter(predicate)
+                .findFirst()
+                .orElseThrow(() -> new AssertionError("Expected reordered 
alternative, found " + plans.size()));
+    }
+
+    private static void assertSemanticsPreserved(Plan original, Plan 
reordered) {
+        Assertions.assertEquals(outputSignature(original), 
outputSignature(reordered));
+        Assertions.assertEquals(conditionSignatures(original), 
conditionSignatures(reordered));
+    }
+
+    private static void assertAliasPlacedOnScan(Plan plan, Alias alias, String 
scanName) {
+        Assertions.assertTrue(anyPlan(plan, candidate -> candidate instanceof 
LogicalProject
+                && ((LogicalProject<?>) 
candidate).getProjects().contains(alias)
+                && scanNames(candidate.child(0)).equals(names(scanName))));
+    }
+
+    private static boolean hasJoin(Plan plan, JoinType type, Set<String> left, 
Set<String> right) {
+        return anyPlan(plan, candidate -> candidate instanceof LogicalJoin
+                && ((LogicalJoin<?, ?>) candidate).getJoinType() == type
+                && scanNames(candidate.child(0)).equals(left)
+                && scanNames(candidate.child(1)).equals(right));
+    }
+
+    private static boolean anyPlan(Plan plan, Predicate<Plan> predicate) {
+        if (predicate.test(plan)) {
+            return true;
+        }
+        for (Plan child : plan.children()) {
+            if (anyPlan(child, predicate)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static Set<String> scanNames(Plan plan) {
+        ImmutableSet.Builder<String> names = ImmutableSet.builder();
+        collectScanNames(plan, names);
+        return names.build();
+    }
+
+    private static void collectScanNames(Plan plan, 
ImmutableSet.Builder<String> names) {
+        if (plan instanceof LogicalOlapScan) {
+            names.add(((LogicalOlapScan) plan).getTable().getName());
+        }
+        plan.children().forEach(child -> collectScanNames(child, names));
+    }
+
+    private static List<String> conditionSignatures(Plan plan) {
+        List<String> signatures = new ArrayList<>();
+        collectConditionSignatures(plan, signatures);
+        Collections.sort(signatures);
+        return signatures;
+    }
+
+    private static void collectConditionSignatures(Plan plan, List<String> 
signatures) {
+        if (plan instanceof LogicalJoin) {
+            LogicalJoin<?, ?> join = (LogicalJoin<?, ?>) plan;
+            
join.getHashJoinConjuncts().stream().map(Expression::toSql).forEach(signatures::add);
+            
join.getOtherJoinConjuncts().stream().map(Expression::toSql).forEach(signatures::add);
+            
join.getMarkJoinConjuncts().stream().map(Expression::toSql).forEach(signatures::add);
+        }
+        plan.children().forEach(child -> collectConditionSignatures(child, 
signatures));
+    }
+
+    private static List<String> outputSignature(Plan plan) {
+        return plan.getOutput().stream()
+                .map(slot -> slot.getExprId() + ":" + slot.getName() + ":" + 
slot.nullable())
+                .collect(Collectors.toList());
+    }
+
+    private static LogicalPlan buildBarePlan(int tableCount) {
+        LogicalPlan plan = scan(0, "t0");
+        for (int i = 1; i < tableCount; i++) {
+            LogicalOlapScan right = scan(i, "t" + i);
+            plan = join(plan, right, JoinType.INNER_JOIN, 2 * (i - 1), 0);
+        }
+        return plan;
+    }
+
+    private static String joinTopology(Plan plan) {
+        if (plan instanceof LogicalProject) {
+            return joinTopology(plan.child(0));
+        }
+        if (plan instanceof LogicalOlapScan) {
+            return ((LogicalOlapScan) plan).getTable().getName();
+        }
+        if (plan instanceof LogicalJoin) {
+            return "(" + joinTopology(plan.child(0)) + "," + 
joinTopology(plan.child(1)) + ")";
+        }
+        return plan.getClass().getSimpleName();
+    }
+
+    private static LogicalOlapScan scan(int id, String name) {
+        return PlanConstructor.newLogicalOlapScan(id, name, 0);
+    }
+
+    private static Alias alias(LogicalOlapScan scan, String name) {
+        return new Alias(new Add(scan.getOutput().get(0), new 
IntegerLiteral(1)), name);
+    }
+
+    private static LogicalPlan join(LogicalPlan left, LogicalPlan right, 
JoinType type,
+            int leftIndex, int rightIndex) {
+        return new LogicalPlanBuilder(left)
+                .join(right, type, Pair.of(leftIndex, rightIndex))
+                .build();
+    }
+
+    private static LogicalPlan project(LogicalPlan child, NamedExpression... 
projects) {
+        return new LogicalProject<>(ImmutableList.copyOf(projects), child);
+    }
+
+    private static LogicalPlan projectAll(LogicalPlan child) {
+        return new LogicalProject<>(ImmutableList.copyOf(child.getOutput()), 
child);
+    }
+
+    private static Set<String> names(String... names) {
+        return ImmutableSet.copyOf(names);
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/ProjectJoinReorderHelperTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/ProjectJoinReorderHelperTest.java
new file mode 100644
index 00000000000..137a146166e
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/exploration/join/ProjectJoinReorderHelperTest.java
@@ -0,0 +1,201 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.exploration.join;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.nereids.rules.Rule;
+import org.apache.doris.nereids.rules.RuleSet;
+import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.trees.expressions.Add;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Random;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.plans.JoinType;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.util.LogicalPlanBuilder;
+import org.apache.doris.nereids.util.PlanConstructor;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+class ProjectJoinReorderHelperTest {
+    private final LogicalOlapScan left = PlanConstructor.newLogicalOlapScan(0, 
"left", 0);
+    private final LogicalOlapScan right = 
PlanConstructor.newLogicalOlapScan(1, "right", 0);
+
+    @Test
+    void keepSlotOnlyProjectUnchanged() {
+        LogicalJoin<LogicalPlan, LogicalPlan> join = join(JoinType.INNER_JOIN);
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> project
+                = new LogicalProject<>(ImmutableList.copyOf(join.getOutput()), 
join);
+
+        Optional<LogicalProject<LogicalJoin<Plan, Plan>>> normalized
+                = ProjectJoinReorderHelper.normalize(project);
+
+        Assertions.assertTrue(normalized.isPresent());
+        Assertions.assertSame(project, normalized.get());
+    }
+
+    @Test
+    void registerStandalonePushDownRulesOnlyAfterDpHyp() {
+        List<RuleType> classicRuleTypes = RuleSet.OTHER_REORDER_RULES.stream()
+                .map(Rule::getRuleType)
+                .collect(Collectors.toList());
+        List<RuleType> afterDpHypRuleTypes = 
RuleSet.AFTER_DPHYP_REORDER_RULES.stream()
+                .map(Rule::getRuleType)
+                .collect(Collectors.toList());
+
+        Assertions.assertFalse(classicRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_INNER_OUTER_JOIN_LEFT));
+        Assertions.assertFalse(classicRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_INNER_OUTER_JOIN_RIGHT));
+        Assertions.assertFalse(classicRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN_LEFT));
+        Assertions.assertFalse(classicRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN_RIGHT));
+        Assertions.assertTrue(afterDpHypRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_INNER_OUTER_JOIN_LEFT));
+        Assertions.assertTrue(afterDpHypRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_INNER_OUTER_JOIN_RIGHT));
+        Assertions.assertTrue(afterDpHypRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN_LEFT));
+        Assertions.assertTrue(afterDpHypRuleTypes.contains(
+                RuleType.PUSH_DOWN_PROJECT_THROUGH_SEMI_JOIN_RIGHT));
+    }
+
+    @Test
+    void pushComplexExpressionsToBothInputsAndRestoreConditionSlots() {
+        LogicalJoin<LogicalPlan, LogicalPlan> join = join(JoinType.INNER_JOIN);
+        Alias leftAlias = new Alias(new Add(left.getOutput().get(1), new 
IntegerLiteral(1)), "left_alias");
+        Alias rightAlias = new Alias(new Add(right.getOutput().get(1), new 
IntegerLiteral(2)), "right_alias");
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> project
+                = new LogicalProject<>(ImmutableList.of(leftAlias, 
rightAlias), join);
+
+        LogicalProject<LogicalJoin<Plan, Plan>> normalized
+                = ProjectJoinReorderHelper.normalize(project).orElseThrow();
+        LogicalJoin<? extends Plan, ? extends Plan> normalizedJoin = 
normalized.child();
+
+        
Assertions.assertTrue(normalized.getProjects().stream().allMatch(Slot.class::isInstance));
+        Assertions.assertEquals(outputSignature(project), 
outputSignature(normalized));
+        Assertions.assertEquals(join.getHashJoinConjuncts(), 
normalizedJoin.getHashJoinConjuncts());
+        assertInputProject(normalizedJoin.left(), leftAlias, 
left.getOutput().get(0));
+        assertInputProject(normalizedJoin.right(), rightAlias, 
right.getOutput().get(0));
+    }
+
+    @Test
+    void rejectHyperEdgeProject() {
+        LogicalJoin<LogicalPlan, LogicalPlan> join = join(JoinType.INNER_JOIN);
+        Alias hyperEdge = new Alias(new Add(left.getOutput().get(0), 
right.getOutput().get(0)), "both");
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> project
+                = new LogicalProject<>(ImmutableList.of(hyperEdge), join);
+
+        
Assertions.assertTrue(ProjectJoinReorderHelper.normalize(project).isEmpty());
+    }
+
+    @Test
+    void respectOuterJoinNullableSides() {
+        Alias leftAlias = new Alias(new Add(left.getOutput().get(1), new 
IntegerLiteral(1)), "left_alias");
+        Alias rightAlias = new Alias(new Add(right.getOutput().get(1), new 
IntegerLiteral(1)), "right_alias");
+
+        LogicalJoin<LogicalPlan, LogicalPlan> leftOuter = 
join(JoinType.LEFT_OUTER_JOIN);
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> leftOnly
+                = new LogicalProject<>(ImmutableList.of(leftAlias, 
leftOuter.getOutput().get(2)), leftOuter);
+        LogicalProject<LogicalJoin<Plan, Plan>> normalized
+                = ProjectJoinReorderHelper.normalize(leftOnly).orElseThrow();
+        Assertions.assertEquals(outputSignature(leftOnly), 
outputSignature(normalized));
+        Assertions.assertFalse(normalized.child().right() instanceof 
LogicalProject);
+
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> 
leftAndNullableRight
+                = new LogicalProject<>(ImmutableList.of(leftAlias, 
rightAlias), leftOuter);
+        
Assertions.assertTrue(ProjectJoinReorderHelper.normalize(leftAndNullableRight).isEmpty());
+
+        LogicalJoin<LogicalPlan, LogicalPlan> rightOuter = 
join(JoinType.RIGHT_OUTER_JOIN);
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> nullableLeft
+                = new LogicalProject<>(ImmutableList.of(leftAlias), 
rightOuter);
+        
Assertions.assertTrue(ProjectJoinReorderHelper.normalize(nullableLeft).isEmpty());
+
+        LogicalJoin<LogicalPlan, LogicalPlan> fullOuter = 
join(JoinType.FULL_OUTER_JOIN);
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> fullOuterProject
+                = new LogicalProject<>(ImmutableList.of(leftAlias, 
rightAlias), fullOuter);
+        
Assertions.assertTrue(ProjectJoinReorderHelper.normalize(fullOuterProject).isEmpty());
+    }
+
+    @Test
+    void pushLeftSemiProjectAndRejectMarkJoin() {
+        LogicalJoin<LogicalPlan, LogicalPlan> semiJoin = 
join(JoinType.LEFT_SEMI_JOIN);
+        Alias alias = new Alias(new Add(left.getOutput().get(1), new 
IntegerLiteral(1)), "semi_alias");
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> project
+                = new LogicalProject<>(ImmutableList.of(alias), semiJoin);
+
+        LogicalProject<LogicalJoin<Plan, Plan>> normalized
+                = ProjectJoinReorderHelper.normalize(project).orElseThrow();
+        assertInputProject(normalized.child().left(), alias, 
left.getOutput().get(0));
+        Assertions.assertEquals(outputSignature(project), 
outputSignature(normalized));
+
+        LogicalJoin<LogicalPlan, LogicalPlan> markJoin = 
(LogicalJoin<LogicalPlan, LogicalPlan>)
+                new LogicalPlanBuilder(left)
+                        .markJoin(right, JoinType.LEFT_SEMI_JOIN, Pair.of(0, 
0))
+                        .build();
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> markProject
+                = new LogicalProject<>(ImmutableList.of(alias), markJoin);
+        
Assertions.assertTrue(ProjectJoinReorderHelper.normalize(markProject).isEmpty());
+    }
+
+    @Test
+    void preserveExistingLiteralAndVolatilePlacementBehavior() {
+        LogicalJoin<LogicalPlan, LogicalPlan> join = join(JoinType.INNER_JOIN);
+        Alias literal = new Alias(new IntegerLiteral(1), "literal_alias");
+        Alias random = new Alias(new Random(), "random_alias");
+        LogicalProject<LogicalJoin<LogicalPlan, LogicalPlan>> project
+                = new LogicalProject<>(ImmutableList.of(literal, random), 
join);
+
+        LogicalProject<LogicalJoin<Plan, Plan>> normalized
+                = ProjectJoinReorderHelper.normalize(project).orElseThrow();
+
+        assertInputProject(normalized.child().left(), literal, random, 
left.getOutput().get(0));
+        Assertions.assertEquals(outputSignature(project), 
outputSignature(normalized));
+    }
+
+    private LogicalJoin<LogicalPlan, LogicalPlan> join(JoinType joinType) {
+        return (LogicalJoin<LogicalPlan, LogicalPlan>) new 
LogicalPlanBuilder(left)
+                .join(right, joinType, Pair.of(0, 0))
+                .build();
+    }
+
+    private static void assertInputProject(Plan input, NamedExpression... 
expectedProjects) {
+        Assertions.assertInstanceOf(LogicalProject.class, input);
+        LogicalProject<?> project = (LogicalProject<?>) input;
+        Assertions.assertEquals(ImmutableList.copyOf(expectedProjects), 
project.getProjects());
+    }
+
+    private static List<String> outputSignature(Plan plan) {
+        return plan.getOutput().stream()
+                .map(slot -> slot.getExprId() + ":" + slot.getName() + ":" + 
slot.nullable())
+                .collect(Collectors.toList());
+    }
+}
diff --git 
a/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query2.out 
b/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query2.out
index 77f572db303..9faf8adc793 100644
--- a/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query2.out
+++ b/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query2.out
@@ -23,16 +23,16 @@ PhysicalCteAnchor ( cteId=CTEId#1 )
 ----------PhysicalProject
 ------------hashJoin[INNER_JOIN broadcast] hashCondition=((date_dim.d_week_seq 
= d_week_seq1)) otherCondition=() build RFs:RF4 d_week_seq->d_week_seq
 --------------PhysicalProject
-----------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 53))) otherCondition=() build RFs:RF3 expr_cast(d_week_seq1 as 
BIGINT)->(cast(d_week_seq as BIGINT) - 53)
+----------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((date_dim.d_week_seq = d_week_seq2)) otherCondition=() build 
RFs:RF3 d_week_seq->d_week_seq
 ------------------PhysicalProject
---------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((date_dim.d_week_seq = d_week_seq2)) otherCondition=() build 
RFs:RF2 d_week_seq->d_week_seq
+--------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 53))) otherCondition=() build RFs:RF2 expr_cast(d_week_seq1 as 
BIGINT)->(cast(d_week_seq as BIGINT) - 53)
 ----------------------PhysicalProject
 ------------------------PhysicalCteConsumer ( cteId=CTEId#1 ) apply RFs: RF2 
RF3
 ----------------------PhysicalProject
-------------------------filter((date_dim.d_year = 1999))
---------------------------PhysicalOlapScan[date_dim]
+------------------------PhysicalCteConsumer ( cteId=CTEId#1 ) apply RFs: RF4
 ------------------PhysicalProject
---------------------PhysicalCteConsumer ( cteId=CTEId#1 ) apply RFs: RF4
+--------------------filter((date_dim.d_year = 1999))
+----------------------PhysicalOlapScan[date_dim]
 --------------PhysicalProject
 ----------------filter((date_dim.d_year = 1998))
 ------------------PhysicalOlapScan[date_dim]
diff --git 
a/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query59.out 
b/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query59.out
index 9f304d51434..3f7818948ef 100644
--- a/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query59.out
+++ b/regression-test/data/shape_check/tpcds_sf100/noStatsRfPrune/query59.out
@@ -19,24 +19,24 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ------------PhysicalProject
 --------------hashJoin[INNER_JOIN broadcast] hashCondition=((d.d_week_seq = 
d_week_seq1)) otherCondition=() build RFs:RF6 d_week_seq->d_week_seq
 ----------------PhysicalProject
-------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 52)) and (y.s_store_id1 = x.s_store_id2)) otherCondition=() build 
RFs:RF4 s_store_id2->s_store_id;RF5 expr_(cast(d_week_seq2 as BIGINT) - 
52)->cast(d_week_seq as BIGINT)
+------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((wss.ss_store_sk = store.s_store_sk) and (y.s_store_id1 = 
x.s_store_id2)) otherCondition=()
 --------------------PhysicalProject
-----------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((wss.ss_store_sk = store.s_store_sk)) otherCondition=() build 
RFs:RF3 s_store_sk->ss_store_sk
+----------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((d.d_week_seq = d_week_seq2)) otherCondition=() build RFs:RF3 
d_week_seq->d_week_seq
 ------------------------PhysicalProject
---------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply RFs: RF3 
RF5 RF6
-------------------------PhysicalProject
---------------------------PhysicalOlapScan[store] apply RFs: RF4
---------------------PhysicalProject
-----------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((d.d_week_seq = d_week_seq2)) otherCondition=() build RFs:RF2 
d_week_seq->d_week_seq
-------------------------PhysicalProject
---------------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((wss.ss_store_sk = store.s_store_sk)) otherCondition=()
+--------------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((wss.ss_store_sk = store.s_store_sk)) otherCondition=()
 ----------------------------PhysicalProject
-------------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply RFs: 
RF2
+------------------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 52))) otherCondition=() build RFs:RF1 expr_cast(d_week_seq1 as 
BIGINT)->(cast(d_week_seq as BIGINT) - 52)
+--------------------------------PhysicalProject
+----------------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply 
RFs: RF1 RF3
+--------------------------------PhysicalProject
+----------------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply 
RFs: RF6
 ----------------------------PhysicalProject
 ------------------------------PhysicalOlapScan[store]
 ------------------------PhysicalProject
 --------------------------filter((d.d_month_seq <= 1219) and (d.d_month_seq >= 
1208))
 ----------------------------PhysicalOlapScan[date_dim(d)]
+--------------------PhysicalProject
+----------------------PhysicalOlapScan[store]
 ----------------PhysicalProject
 ------------------filter((d.d_month_seq <= 1207) and (d.d_month_seq >= 1196))
 --------------------PhysicalOlapScan[date_dim(d)]
diff --git 
a/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query2.out 
b/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query2.out
index 1254fcb8708..e14db5d1acd 100644
--- a/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query2.out
+++ b/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query2.out
@@ -23,16 +23,16 @@ PhysicalCteAnchor ( cteId=CTEId#1 )
 ----------PhysicalProject
 ------------hashJoin[INNER_JOIN broadcast] hashCondition=((date_dim.d_week_seq 
= d_week_seq1)) otherCondition=() build RFs:RF4 d_week_seq->d_week_seq
 --------------PhysicalProject
-----------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 53))) otherCondition=() build RFs:RF3 expr_cast(d_week_seq1 as 
BIGINT)->(cast(d_week_seq as BIGINT) - 53)
+----------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((date_dim.d_week_seq = d_week_seq2)) otherCondition=() build 
RFs:RF3 d_week_seq->d_week_seq
 ------------------PhysicalProject
---------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((date_dim.d_week_seq = d_week_seq2)) otherCondition=() build 
RFs:RF2 d_week_seq->d_week_seq
+--------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 53))) otherCondition=() build RFs:RF2 expr_cast(d_week_seq1 as 
BIGINT)->(cast(d_week_seq as BIGINT) - 53)
 ----------------------PhysicalProject
 ------------------------PhysicalCteConsumer ( cteId=CTEId#1 ) apply RFs: RF2 
RF3
 ----------------------PhysicalProject
-------------------------filter((date_dim.d_year = 1999))
---------------------------PhysicalOlapScan[date_dim]
+------------------------PhysicalCteConsumer ( cteId=CTEId#1 ) apply RFs: RF4
 ------------------PhysicalProject
---------------------PhysicalCteConsumer ( cteId=CTEId#1 ) apply RFs: RF4
+--------------------filter((date_dim.d_year = 1999))
+----------------------PhysicalOlapScan[date_dim]
 --------------PhysicalProject
 ----------------filter((date_dim.d_year = 1998))
 ------------------PhysicalOlapScan[date_dim]
diff --git 
a/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query59.out 
b/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query59.out
index 844afc30211..a0009b36800 100644
--- a/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query59.out
+++ b/regression-test/data/shape_check/tpcds_sf100/no_stats_shape/query59.out
@@ -19,24 +19,24 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
 ------------PhysicalProject
 --------------hashJoin[INNER_JOIN broadcast] hashCondition=((d.d_week_seq = 
d_week_seq1)) otherCondition=() build RFs:RF6 d_week_seq->d_week_seq
 ----------------PhysicalProject
-------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 52)) and (y.s_store_id1 = x.s_store_id2)) otherCondition=() build 
RFs:RF4 s_store_id2->s_store_id;RF5 expr_(cast(d_week_seq2 as BIGINT) - 
52)->cast(d_week_seq as BIGINT)
+------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((wss.ss_store_sk = store.s_store_sk) and (y.s_store_id1 = 
x.s_store_id2)) otherCondition=() build RFs:RF4 s_store_id1->s_store_id;RF5 
s_store_sk->ss_store_sk
 --------------------PhysicalProject
-----------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((wss.ss_store_sk = store.s_store_sk)) otherCondition=() build 
RFs:RF3 s_store_sk->ss_store_sk
+----------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((d.d_week_seq = d_week_seq2)) otherCondition=() build RFs:RF3 
d_week_seq->d_week_seq
 ------------------------PhysicalProject
---------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply RFs: RF3 
RF5 RF6
-------------------------PhysicalProject
---------------------------PhysicalOlapScan[store] apply RFs: RF4
---------------------PhysicalProject
-----------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((d.d_week_seq = d_week_seq2)) otherCondition=() build RFs:RF2 
d_week_seq->d_week_seq
-------------------------PhysicalProject
---------------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((wss.ss_store_sk = store.s_store_sk)) otherCondition=() build 
RFs:RF1 s_store_sk->ss_store_sk
+--------------------------hashJoin[INNER_JOIN broadcast] 
hashCondition=((wss.ss_store_sk = store.s_store_sk)) otherCondition=() build 
RFs:RF2 s_store_sk->ss_store_sk
 ----------------------------PhysicalProject
-------------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply RFs: 
RF1 RF2
+------------------------------hashJoin[INNER_JOIN shuffle] 
hashCondition=((expr_cast(d_week_seq1 as BIGINT) = expr_(cast(d_week_seq2 as 
BIGINT) - 52))) otherCondition=() build RFs:RF1 expr_cast(d_week_seq1 as 
BIGINT)->(cast(d_week_seq as BIGINT) - 52)
+--------------------------------PhysicalProject
+----------------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply 
RFs: RF1 RF2 RF3
+--------------------------------PhysicalProject
+----------------------------------PhysicalCteConsumer ( cteId=CTEId#0 ) apply 
RFs: RF5 RF6
 ----------------------------PhysicalProject
-------------------------------PhysicalOlapScan[store]
+------------------------------PhysicalOlapScan[store] apply RFs: RF4
 ------------------------PhysicalProject
 --------------------------filter((d.d_month_seq <= 1219) and (d.d_month_seq >= 
1208))
 ----------------------------PhysicalOlapScan[date_dim(d)]
+--------------------PhysicalProject
+----------------------PhysicalOlapScan[store]
 ----------------PhysicalProject
 ------------------filter((d.d_month_seq <= 1207) and (d.d_month_seq >= 1196))
 --------------------PhysicalOlapScan[date_dim(d)]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to