diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java
index 6fff27ede5..641f4f1742 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/NereidsRewriteJobExecutor.java
@@ -27,6 +27,7 @@ import org.apache.doris.nereids.rules.mv.SelectMaterializedIndexWithoutAggregate
import org.apache.doris.nereids.rules.rewrite.logical.ColumnPruning;
import org.apache.doris.nereids.rules.rewrite.logical.EliminateFilter;
import org.apache.doris.nereids.rules.rewrite.logical.EliminateLimit;
+import org.apache.doris.nereids.rules.rewrite.logical.EliminateUnnecessaryProject;
import org.apache.doris.nereids.rules.rewrite.logical.ExtractSingleTableExpressionFromDisjunction;
import org.apache.doris.nereids.rules.rewrite.logical.FindHashConditionForJoin;
import org.apache.doris.nereids.rules.rewrite.logical.LimitPushDown;
@@ -75,6 +76,9 @@ public class NereidsRewriteJobExecutor extends BatchRulesJob {
.add(topDownBatch(ImmutableList.of(new PruneOlapScanPartition())))
.add(topDownBatch(ImmutableList.of(new SelectMaterializedIndexWithAggregate())))
.add(topDownBatch(ImmutableList.of(new SelectMaterializedIndexWithoutAggregate())))
+ // we need to execute this rule at the end of rewrite
+ // to avoid two consecutive same project appear when we do optimization.
+ .add(topDownBatch(ImmutableList.of(new EliminateUnnecessaryProject())))
.build();
rulesJob.addAll(jobs);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
index 9728b49b38..2ee123c482 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
@@ -58,6 +58,7 @@ public enum RuleType {
NORMALIZE_AGGREGATE(RuleTypeClass.REWRITE),
AGGREGATE_DISASSEMBLE(RuleTypeClass.REWRITE),
COLUMN_PRUNE_PROJECTION(RuleTypeClass.REWRITE),
+ ELIMINATE_UNNECESSARY_PROJECT(RuleTypeClass.REWRITE),
ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
PROJECT_ELIMINATE_ALIAS_NODE(RuleTypeClass.REWRITE),
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProject.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProject.java
new file mode 100644
index 0000000000..675560fd7e
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProject.java
@@ -0,0 +1,51 @@
+// 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.rewrite.logical;
+
+import org.apache.doris.nereids.rules.Rule;
+import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.rules.rewrite.OneRewriteRuleFactory;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+
+/**
+ * remove the project that output same with its child to avoid we get two consecutive projects in best plan.
+ * for more information, please see this PR
+ */
+public class EliminateUnnecessaryProject extends OneRewriteRuleFactory {
+
+ @Override
+ public Rule build() {
+ return logicalProject(any())
+ .when(project -> project.getOutputSet().equals(project.child().getOutputSet()))
+ .thenApply(ctx -> {
+ int rootGroupId = ctx.cascadesContext.getMemo().getRoot().getGroupId().asInt();
+ LogicalProject project = ctx.root;
+ // if project is root, we need to ensure the output order is same.
+ if (project.getGroupExpression().get().getOwnerGroup().getGroupId().asInt() == rootGroupId) {
+ if (project.getOutput().equals(project.child().getOutput())) {
+ return project.child();
+ } else {
+ return null;
+ }
+ } else {
+ return project.child();
+ }
+ }).toRule(RuleType.ELIMINATE_UNNECESSARY_PROJECT);
+ }
+}
diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProjectTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProjectTest.java
new file mode 100644
index 0000000000..babcd5caf6
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/logical/EliminateUnnecessaryProjectTest.java
@@ -0,0 +1,85 @@
+// 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.rewrite.logical;
+
+import org.apache.doris.nereids.CascadesContext;
+import org.apache.doris.nereids.rules.Rule;
+import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
+import org.apache.doris.nereids.trees.plans.Plan;
+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.PlanConstructor;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+/**
+ * test ELIMINATE_UNNECESSARY_PROJECT rule.
+ */
+public class EliminateUnnecessaryProjectTest {
+
+ @Test
+ public void testEliminateNonTopUnnecessaryProject() {
+ LogicalPlan unnecessaryProject = new LogicalPlanBuilder(PlanConstructor.newLogicalOlapScan(0, "t1", 0))
+ .project(ImmutableList.of(1, 0))
+ .filter(BooleanLiteral.FALSE)
+ .build();
+
+ CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(unnecessaryProject);
+ List rules = Lists.newArrayList(new EliminateUnnecessaryProject().build());
+ cascadesContext.topDownRewrite(rules);
+
+ Plan actual = cascadesContext.getMemo().copyOut();
+ Assertions.assertTrue(actual.child(0) instanceof LogicalOlapScan);
+ }
+
+ @Test
+ public void testEliminateTopUnnecessaryProject() {
+ LogicalPlan unnecessaryProject = new LogicalPlanBuilder(PlanConstructor.newLogicalOlapScan(0, "t1", 0))
+ .project(ImmutableList.of(0, 1))
+ .build();
+
+ CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(unnecessaryProject);
+ List rules = Lists.newArrayList(new EliminateUnnecessaryProject().build());
+ cascadesContext.topDownRewrite(rules);
+
+ Plan actual = cascadesContext.getMemo().copyOut();
+ Assertions.assertTrue(actual instanceof LogicalOlapScan);
+ }
+
+ @Test
+ public void testNotEliminateTopProjectWhenOutputNotEquals() {
+ LogicalPlan unnecessaryProject = new LogicalPlanBuilder(PlanConstructor.newLogicalOlapScan(0, "t1", 0))
+ .project(ImmutableList.of(1, 0))
+ .build();
+
+ CascadesContext cascadesContext = MemoTestUtils.createCascadesContext(unnecessaryProject);
+ List rules = Lists.newArrayList(new EliminateUnnecessaryProject().build());
+ cascadesContext.topDownRewrite(rules);
+
+ Plan actual = cascadesContext.getMemo().copyOut();
+ Assertions.assertTrue(actual instanceof LogicalProject);
+ }
+}