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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 8475be88741 branch-4.1: [fix](dereference) Resolve correlated 
qualified columns before dereference (#67438)  (#68260) (#68062)
8475be88741 is described below

commit 8475be88741ab0e688d5c7b7f96006d5ea1f3fe1
Author: Calvin Kirs <[email protected]>
AuthorDate: Wed Sep 23 10:24:09 2026 +0800

    branch-4.1: [fix](dereference) Resolve correlated qualified columns before 
dereference (#67438)  (#68260) (#68062)
    
    https://github.com/apache/doris/pull/67438
    https://github.com/apache/doris/pull/68260
---
 .../org/apache/doris/nereids/analyzer/Scope.java   |  19 ++
 .../nereids/rules/analysis/BindExpression.java     | 159 +++++++----
 .../nereids/rules/analysis/ExpressionAnalyzer.java | 221 ++++++++++++++--
 ...ProjectOtherJoinConditionForNestedLoopJoin.java |  10 +
 .../apache/doris/nereids/analyzer/ScopeTest.java   |  48 ++++
 .../rules/analysis/ExpressionAnalyzerTest.java     |  51 ++++
 .../nereids/rules/analysis/TestDereference.java    | 285 ++++++++++++++++++++
 ...ectOtherJoinConditionForNestedLoopJoinTest.java |  56 ++++
 regression-test/data/query_p0/test_dereference.out |  99 +++++++
 .../suites/query_p0/test_dereference.groovy        | 293 ++++++++++++++++++++-
 10 files changed, 1166 insertions(+), 75 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/Scope.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/Scope.java
index cb74698a62b..58590e74f02 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/Scope.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/analyzer/Scope.java
@@ -21,6 +21,7 @@ import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.util.Utils;
 
 import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
@@ -66,6 +67,7 @@ public class Scope {
     private final boolean buildNameToSlot;
     private final Supplier<ListMultimap<String, Slot>> nameToSlot;
     private final Supplier<ListMultimap<String, Slot>> nameToAsteriskSlot;
+    private final Supplier<ImmutableSetMultimap<String, List<String>>> 
relationNameToQualifiers;
 
     public Scope(List<Slot> slots) {
         this(Optional.empty(), slots);
@@ -87,6 +89,7 @@ public class Scope {
         this.buildNameToSlot = slots.size() > 500;
         this.nameToSlot = buildNameToSlot ? 
Suppliers.memoize(this::buildNameToSlot) : null;
         this.nameToAsteriskSlot = buildNameToSlot ? 
Suppliers.memoize(this::buildNameToAsteriskSlot) : null;
+        this.relationNameToQualifiers = 
Suppliers.memoize(this::buildRelationNameToQualifiers);
         this.asteriskSlots = Utils.fastToImmutableList(
                 Objects.requireNonNull(asteriskSlots, "asteriskSlots can not 
be null"));
     }
@@ -107,6 +110,11 @@ public class Scope {
         return correlatedSlots;
     }
 
+    /** Find distinct relation qualifiers by relation name, ignoring case. */
+    public Set<List<String>> findRelationQualifiersIgnoreCase(String 
relationName) {
+        return 
relationNameToQualifiers.get().get(relationName.toUpperCase(Locale.ROOT));
+    }
+
     /** findSlotIgnoreCase */
     public List<Slot> findSlotIgnoreCase(String slotName, boolean all) {
         List<Slot> slots = all ? this.slots : this.asteriskSlots;
@@ -140,4 +148,15 @@ public class Scope {
         }
         return map;
     }
+
+    private ImmutableSetMultimap<String, List<String>> 
buildRelationNameToQualifiers() {
+        ImmutableSetMultimap.Builder<String, List<String>> builder = 
ImmutableSetMultimap.builder();
+        for (Slot slot : slots) {
+            if (!slot.getQualifier().isEmpty()) {
+                List<String> qualifier = slot.getQualifier();
+                builder.put(qualifier.get(qualifier.size() - 
1).toUpperCase(Locale.ROOT), qualifier);
+            }
+        }
+        return builder.build();
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
index 02737980a3a..f6b4d49396b 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
@@ -669,7 +669,8 @@ public class BindExpression implements AnalysisRuleFactory {
         Supplier<CustomSlotBinderAnalyzer> bindByAggChild = 
Suppliers.memoize(() -> {
             Scope aggChildOutputScope
                     = toScope(cascadesContext, 
PlanUtils.fastGetChildrenOutputs(aggregate.children()));
-            return (analyzer, unboundSlot) -> 
analyzer.bindSlotByScope(unboundSlot, aggChildOutputScope);
+            return (analyzer, unboundSlot, bindRelationQualifierOnly) ->
+                    analyzer.bindSlotByScope(unboundSlot, aggChildOutputScope, 
bindRelationQualifierOnly);
         });
 
         Scope aggOutputScope = toScope(cascadesContext, aggregate.getOutput());
@@ -684,19 +685,23 @@ public class BindExpression implements 
AnalysisRuleFactory {
             }
             Scope groupBySlotsScope = toScope(cascadesContext, 
groupBySlots.build());
 
-            return (analyzer, unboundSlot) -> {
-                List<Expression> boundInGroupBy = 
analyzer.bindSlotByScope(unboundSlot, groupBySlotsScope);
-                if (!boundInGroupBy.isEmpty()) {
-                    return ImmutableList.of(boundInGroupBy.get(0));
+            return (analyzer, unboundSlot, bindRelationQualifierOnly) -> {
+                ExpressionAnalyzer.SlotBinding boundInGroupBy = 
analyzer.bindSlotByScope(
+                        unboundSlot, groupBySlotsScope, 
bindRelationQualifierOnly);
+                if (!boundInGroupBy.getBoundSlots().isEmpty()) {
+                    return boundInGroupBy.firstOrEmpty();
                 }
 
-                List<Expression> boundInAggOutput = 
analyzer.bindSlotByScope(unboundSlot, aggOutputScope);
-                if (!boundInAggOutput.isEmpty()) {
-                    return ImmutableList.of(boundInAggOutput.get(0));
+                ExpressionAnalyzer.SlotBinding boundInAggOutput = 
analyzer.bindSlotByScope(
+                        unboundSlot, aggOutputScope, 
bindRelationQualifierOnly);
+                if (!boundInAggOutput.getBoundSlots().isEmpty()) {
+                    return 
boundInAggOutput.firstOrEmpty().withQualifierOccupancyFrom(boundInGroupBy);
                 }
 
-                List<? extends Expression> expressions = 
bindByAggChild.get().bindSlot(analyzer, unboundSlot);
-                return expressions.isEmpty() ? expressions : 
ImmutableList.of(expressions.get(0));
+                return bindByAggChild.get().bindSlot(analyzer, unboundSlot, 
bindRelationQualifierOnly)
+                        .firstOrEmpty()
+                        .withQualifierOccupancyFrom(boundInGroupBy)
+                        .withQualifierOccupancyFrom(boundInAggOutput);
             };
         });
 
@@ -737,9 +742,19 @@ public class BindExpression implements AnalysisRuleFactory 
{
             @Override
             protected List<? extends Expression> 
bindSlotByThisScope(UnboundSlot unboundSlot) {
                 if (currentIsInAggregateFunction) {
-                    return bindByAggChild.get().bindSlot(this, unboundSlot);
+                    return bindByAggChild.get().bindSlot(this, unboundSlot, 
false).getBoundSlots();
                 } else {
-                    return 
bindByGroupByThenAggOutputThenAggChild.get().bindSlot(this, unboundSlot);
+                    return bindByGroupByThenAggOutputThenAggChild.get()
+                            .bindSlot(this, unboundSlot, 
false).getBoundSlots();
+                }
+            }
+
+            @Override
+            protected SlotBinding 
bindSlotByRelationQualifierInThisScope(UnboundSlot unboundSlot) {
+                if (currentIsInAggregateFunction) {
+                    return bindByAggChild.get().bindSlot(this, unboundSlot, 
true);
+                } else {
+                    return 
bindByGroupByThenAggOutputThenAggChild.get().bindSlot(this, unboundSlot, true);
                 }
             }
         };
@@ -768,12 +783,14 @@ public class BindExpression implements 
AnalysisRuleFactory {
 
         SimpleExprAnalyzer analyzer = buildCustomSlotBinderAnalyzer(
                 having, cascadesContext, defaultScope, false, true,
-                (self, unboundSlot) -> {
-                    List<Expression> slots = self.bindSlotByScope(unboundSlot, 
defaultScope);
-                    if (!slots.isEmpty()) {
+                (self, unboundSlot, bindRelationQualifierOnly) -> {
+                    ExpressionAnalyzer.SlotBinding slots = 
self.bindSlotByScope(
+                            unboundSlot, defaultScope, 
bindRelationQualifierOnly);
+                    if (!slots.getBoundSlots().isEmpty()) {
                         return slots;
                     }
-                    return self.bindSlotByScope(unboundSlot, 
backupScope.get());
+                    return self.bindSlotByScope(unboundSlot, 
backupScope.get(), bindRelationQualifierOnly)
+                            .withQualifierOccupancyFrom(slots);
                 });
         ImmutableSet.Builder<Expression> boundConjuncts = 
ImmutableSet.builder();
         Map<Expression, Expression> bindUniqueIdReplaceMap = 
getBelowAggregateGroupByUniqueFuncReplaceMap(having);
@@ -1264,12 +1281,14 @@ public class BindExpression implements 
AnalysisRuleFactory {
 
         SimpleExprAnalyzer analyzer = buildCustomSlotBinderAnalyzer(
                 qualify, cascadesContext, defaultScope.get(), true, true,
-                (self, unboundSlot) -> {
-                List<Expression> slots = self.bindSlotByScope(unboundSlot, 
defaultScope.get());
-                if (!slots.isEmpty()) {
-                    return slots;
-                }
-                return self.bindSlotByScope(unboundSlot, backupScope);
+                (self, unboundSlot, bindRelationQualifierOnly) -> {
+                    ExpressionAnalyzer.SlotBinding slots = 
self.bindSlotByScope(
+                            unboundSlot, defaultScope.get(), 
bindRelationQualifierOnly);
+                    if (!slots.getBoundSlots().isEmpty()) {
+                        return slots;
+                    }
+                    return self.bindSlotByScope(unboundSlot, backupScope, 
bindRelationQualifierOnly)
+                            .withQualifierOccupancyFrom(slots);
                 });
         Map<Expression, Expression> bindUniqueIdReplaceMap = 
getBelowAggregateGroupByUniqueFuncReplaceMap(qualify);
         for (Expression expr : qualify.getConjuncts()) {
@@ -1289,7 +1308,8 @@ public class BindExpression implements 
AnalysisRuleFactory {
         Supplier<CustomSlotBinderAnalyzer> bindByAggChild = 
Suppliers.memoize(() -> {
             Scope aggChildOutputScope
                     = toScope(cascadesContext, 
PlanUtils.fastGetChildrenOutputs(aggregate.children()));
-            return (analyzer, unboundSlot) -> 
analyzer.bindSlotByScope(unboundSlot, aggChildOutputScope);
+            return (analyzer, unboundSlot, bindRelationQualifierOnly) ->
+                    analyzer.bindSlotByScope(unboundSlot, aggChildOutputScope, 
bindRelationQualifierOnly);
         });
         Scope aggOutputScope = toScope(cascadesContext, aggregate.getOutput());
         Supplier<CustomSlotBinderAnalyzer> 
bindByGroupByThenAggOutputThenAggChildOutput = Suppliers.memoize(() -> {
@@ -1302,17 +1322,21 @@ public class BindExpression implements 
AnalysisRuleFactory {
             }
             Scope groupBySlotsScope = toScope(cascadesContext, 
groupBySlots.build());
 
-            return (analyzer, unboundSlot) -> {
-                List<Expression> boundInGroupBy = 
analyzer.bindSlotByScope(unboundSlot, groupBySlotsScope);
-                if (!boundInGroupBy.isEmpty()) {
-                    return ImmutableList.of(boundInGroupBy.get(0));
+            return (analyzer, unboundSlot, bindRelationQualifierOnly) -> {
+                ExpressionAnalyzer.SlotBinding boundInGroupBy = 
analyzer.bindSlotByScope(
+                        unboundSlot, groupBySlotsScope, 
bindRelationQualifierOnly);
+                if (!boundInGroupBy.getBoundSlots().isEmpty()) {
+                    return boundInGroupBy.firstOrEmpty();
                 }
-                List<Expression> boundInAggOutput = 
analyzer.bindSlotByScope(unboundSlot, aggOutputScope);
-                if (!boundInAggOutput.isEmpty()) {
-                    return ImmutableList.of(boundInAggOutput.get(0));
+                ExpressionAnalyzer.SlotBinding boundInAggOutput = 
analyzer.bindSlotByScope(
+                        unboundSlot, aggOutputScope, 
bindRelationQualifierOnly);
+                if (!boundInAggOutput.getBoundSlots().isEmpty()) {
+                    return 
boundInAggOutput.firstOrEmpty().withQualifierOccupancyFrom(boundInGroupBy);
                 }
-                List<? extends Expression> expressions = 
bindByAggChild.get().bindSlot(analyzer, unboundSlot);
-                return expressions.isEmpty() ? expressions : 
ImmutableList.of(expressions.get(0));
+                return bindByAggChild.get().bindSlot(analyzer, unboundSlot, 
bindRelationQualifierOnly)
+                        .firstOrEmpty()
+                        .withQualifierOccupancyFrom(boundInGroupBy)
+                        .withQualifierOccupancyFrom(boundInAggOutput);
             };
         });
 
@@ -1320,7 +1344,13 @@ public class BindExpression implements 
AnalysisRuleFactory {
                 true, true) {
             @Override
             protected List<? extends Expression> 
bindSlotByThisScope(UnboundSlot unboundSlot) {
-                return 
bindByGroupByThenAggOutputThenAggChildOutput.get().bindSlot(this, unboundSlot);
+                return bindByGroupByThenAggOutputThenAggChildOutput.get()
+                        .bindSlot(this, unboundSlot, false).getBoundSlots();
+            }
+
+            @Override
+            protected SlotBinding 
bindSlotByRelationQualifierInThisScope(UnboundSlot unboundSlot) {
+                return 
bindByGroupByThenAggOutputThenAggChildOutput.get().bindSlot(this, unboundSlot, 
true);
             }
         };
 
@@ -1626,28 +1656,30 @@ public class BindExpression implements 
AnalysisRuleFactory {
 
         SimpleExprAnalyzer analyzer = buildCustomSlotBinderAnalyzer(
                 agg, cascadesContext, childOutputScope, true, true,
-                (self, unboundSlot) -> {
+                (self, unboundSlot, bindRelationQualifierOnly) -> {
                     // see: https://github.com/apache/doris/pull/15240
                     //
                     // first, try to bind by agg.child.output
-                    List<Expression> slotsInChildren = 
self.bindExactSlotsByThisScope(unboundSlot, childOutputScope);
-                    if (slotsInChildren.size() == 1) {
+                    ExpressionAnalyzer.SlotBinding slotsInChildren = 
self.bindExactSlotsByThisScope(
+                            unboundSlot, childOutputScope, 
bindRelationQualifierOnly);
+                    if (slotsInChildren.getBoundSlots().size() == 1) {
                         // bind succeed
                         return slotsInChildren;
                     }
                     // second, bind failed:
                     // if the slot not found, or more than one candidate slots 
found in agg.child.output,
                     // then try to bind by agg.output
-                    List<Expression> slotsInOutput = 
self.bindExactSlotsByThisScope(
-                            unboundSlot, aggOutputScopeWithoutAggFun.get());
-                    if (slotsInOutput.isEmpty()) {
+                    ExpressionAnalyzer.SlotBinding slotsInOutput = 
self.bindExactSlotsByThisScope(
+                            unboundSlot, aggOutputScopeWithoutAggFun.get(), 
bindRelationQualifierOnly);
+                    if (slotsInOutput.getBoundSlots().isEmpty()) {
                         // if slotsInChildren.size() > 1 && 
slotsInOutput.isEmpty(),
                         // we return slotsInChildren to throw an ambiguous 
slots exception
-                        return slotsInChildren;
+                        return 
slotsInChildren.withQualifierOccupancyFrom(slotsInOutput);
                     }
 
-                    Builder<Expression> useOutputExpr = 
ImmutableList.builderWithExpectedSize(slotsInOutput.size());
-                    for (Expression slotInOutput : slotsInOutput) {
+                    Builder<Expression> useOutputExpr = 
ImmutableList.builderWithExpectedSize(
+                            slotsInOutput.getBoundSlots().size());
+                    for (Expression slotInOutput : 
slotsInOutput.getBoundSlots()) {
                         // mappingSlot is provided by 
aggOutputScopeWithoutAggFun
                         // and no non-MappingSlot slot exist in the Scope, so 
we
                         // can direct cast it safely
@@ -1664,7 +1696,9 @@ public class BindExpression implements 
AnalysisRuleFactory {
                         // we should rewrite to: select k + 1 as k1 from tbl 
group by k + 1
                         useOutputExpr.add(mappingSlot.getMappingExpression());
                     }
-                    return useOutputExpr.build();
+                    return new 
ExpressionAnalyzer.SlotBinding(useOutputExpr.build(), false)
+                            .withQualifierOccupancyFrom(slotsInChildren)
+                            .withQualifierOccupancyFrom(slotsInOutput);
                 });
 
         ImmutableList.Builder<Expression> boundGroupByBuilder = 
ImmutableList.builderWithExpectedSize(groupBy.size());
@@ -1741,17 +1775,20 @@ public class BindExpression implements 
AnalysisRuleFactory {
                 () -> toScope(cascadesContext, 
PlanUtils.fastGetChildrenOutputs(finalInput.children())));
         SimpleExprAnalyzer bindInInputScopeThenInputChildScope = 
buildCustomSlotBinderAnalyzer(
                 sort, cascadesContext, inputScope, true, false,
-                (self, unboundSlot) -> {
+                (self, unboundSlot, bindRelationQualifierOnly) -> {
                     // first, try to bind slot in Scope(input.output)
-                    List<Expression> slotsInInput = 
self.bindExactSlotsByThisScope(unboundSlot, inputScope);
-                    if (!slotsInInput.isEmpty()) {
+                    ExpressionAnalyzer.SlotBinding slotsInInput = 
self.bindExactSlotsByThisScope(
+                            unboundSlot, inputScope, 
bindRelationQualifierOnly);
+                    if (!slotsInInput.getBoundSlots().isEmpty()) {
                         // bind succeed
-                        return ImmutableList.of(slotsInInput.get(0));
+                        return slotsInInput.firstOrEmpty();
                     }
                     // second, bind failed:
                     // if the slot not found, or more than one candidate slots 
found in input.output,
                     // then try to bind by input.children.output
-                    return self.bindExactSlotsByThisScope(unboundSlot, 
inputChildrenScope.get());
+                    return self.bindExactSlotsByThisScope(
+                            unboundSlot, inputChildrenScope.get(), 
bindRelationQualifierOnly)
+                            .withQualifierOccupancyFrom(slotsInInput);
                 });
 
         SimpleExprAnalyzer bindInInputChildScope = 
getAnalyzerForOrderByAggFunc(finalInput, cascadesContext, sort,
@@ -1947,7 +1984,12 @@ public class BindExpression implements 
AnalysisRuleFactory {
                 enableExactMatch, bindSlotInOuterScope) {
             @Override
             protected List<? extends Expression> 
bindSlotByThisScope(UnboundSlot unboundSlot) {
-                return customSlotBinder.bindSlot(this, unboundSlot);
+                return customSlotBinder.bindSlot(this, unboundSlot, 
false).getBoundSlots();
+            }
+
+            @Override
+            protected SlotBinding 
bindSlotByRelationQualifierInThisScope(UnboundSlot unboundSlot) {
+                return customSlotBinder.bindSlot(this, unboundSlot, true);
             }
         };
         return expr -> expressionAnalyzer.analyze(expr, rewriteContext);
@@ -1975,7 +2017,8 @@ public class BindExpression implements 
AnalysisRuleFactory {
     }
 
     private interface CustomSlotBinderAnalyzer {
-        List<? extends Expression> bindSlot(ExpressionAnalyzer analyzer, 
UnboundSlot unboundSlot);
+        ExpressionAnalyzer.SlotBinding bindSlot(
+                ExpressionAnalyzer analyzer, UnboundSlot unboundSlot, boolean 
bindRelationQualifierOnly);
     }
 
     public String toSqlWithBackquote(List<Slot> slots) {
@@ -2015,15 +2058,19 @@ public class BindExpression implements 
AnalysisRuleFactory {
         Scope outputWithoutAggFunc = toScope(cascadesContext, 
outputSlots.build());
         SimpleExprAnalyzer bindInInputChildScope = 
buildCustomSlotBinderAnalyzer(
                 sort, cascadesContext, inputScope, true, false,
-                (analyzer, unboundSlot) -> {
+                (analyzer, unboundSlot, bindRelationQualifierOnly) -> {
                     if (finalInput instanceof LogicalAggregate) {
-                        List<Expression> boundInOutputWithoutAggFunc = 
analyzer.bindSlotByScope(unboundSlot,
-                                outputWithoutAggFunc);
-                        if (!boundInOutputWithoutAggFunc.isEmpty()) {
-                            return 
ImmutableList.of(boundInOutputWithoutAggFunc.get(0));
+                        ExpressionAnalyzer.SlotBinding 
boundInOutputWithoutAggFunc = analyzer.bindSlotByScope(
+                                unboundSlot, outputWithoutAggFunc, 
bindRelationQualifierOnly);
+                        if 
(!boundInOutputWithoutAggFunc.getBoundSlots().isEmpty()) {
+                            return boundInOutputWithoutAggFunc.firstOrEmpty();
                         }
+                        return analyzer.bindExactSlotsByThisScope(
+                                unboundSlot, inputChildrenScope.get(), 
bindRelationQualifierOnly)
+                                
.withQualifierOccupancyFrom(boundInOutputWithoutAggFunc);
                     }
-                    return analyzer.bindExactSlotsByThisScope(unboundSlot, 
inputChildrenScope.get());
+                    return analyzer.bindExactSlotsByThisScope(
+                            unboundSlot, inputChildrenScope.get(), 
bindRelationQualifierOnly);
                 });
         return bindInInputChildScope;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
index 8a3eade3f20..73049cf58c9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
@@ -120,6 +120,8 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 
@@ -286,14 +288,38 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
     @Override
     public Expression visitUnboundSlot(UnboundSlot unboundSlot, 
ExpressionRewriteContext context) {
         Optional<Scope> outerScope = getScope().getOuterScope();
-        Optional<List<? extends Expression>> boundedOpt = 
Optional.of(bindSlotByThisScope(unboundSlot));
-        boolean foundInThisScope = !boundedOpt.get().isEmpty();
+        List<? extends Expression> bounded = ImmutableList.of();
+        boolean foundInThisScope = false;
+        boolean canBindOuterScope = bindSlotInOuterScope && 
outerScope.isPresent();
+        boolean relationQualifierOccupied = false;
+
+        // A multipart name can be either a relation-qualified column (t.col) 
or a nested field
+        // reference (col.field). Try the relation-qualified interpretation in 
every visible scope
+        // first, so a nearer name "t" does not hide a farther relation alias 
"t". The visible scopes
+        // are the local ones, which HAVING, QUALIFY and ORDER BY layer from 
the select output and its
+        // child output in a clause specific order, and then the outer scope 
of a correlated subquery:
+        //   select q.v as q from t q order by q.v  -- q.v is the column of 
relation q, not alias q
+        if (shouldPrioritizeRelationQualifier() && 
unboundSlot.getNameParts().size() > 1) {
+            SlotBinding localRelationBinding = 
bindSlotByRelationQualifierInThisScope(unboundSlot);
+            bounded = localRelationBinding.getBoundSlots();
+            foundInThisScope = !bounded.isEmpty();
+            if (!foundInThisScope && canBindOuterScope) {
+                relationQualifierOccupied = 
localRelationBinding.isRelationQualifierOccupied();
+                if (!relationQualifierOccupied) {
+                    bounded = bindSlotsByRelationQualifier(unboundSlot, 
outerScope.get());
+                }
+            }
+        }
+
+        if (bounded.isEmpty()) {
+            bounded = bindSlotByThisScope(unboundSlot);
+            foundInThisScope = !bounded.isEmpty();
+        }
         // Currently only looking for symbols on the previous level.
-        if (bindSlotInOuterScope && !foundInThisScope && 
outerScope.isPresent()) {
-            boundedOpt = Optional.of(bindSlotByScope(unboundSlot, 
outerScope.get()));
+        if (canBindOuterScope && bounded.isEmpty() && 
!relationQualifierOccupied) {
+            bounded = bindSlotByScope(unboundSlot, outerScope.get());
         }
         // it is heavy to deduplicate slots in scope. So we deduplicates 
bounded here
-        List<? extends Expression> bounded = boundedOpt.get();
         if (bounded.size() > 1) {
             bounded = bounded.stream().distinct().collect(Collectors.toList());
         }
@@ -307,14 +333,15 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
                 return unboundSlot;
             case 1:
                 Expression firstBound = bounded.get(0);
-                if (!foundInThisScope && firstBound instanceof Slot
-                        && 
!outerScope.get().getCorrelatedSlots().contains(firstBound)) {
+                Set<Slot> inputSlots = firstBound.getInputSlots();
+                if (!foundInThisScope
+                        && 
!outerScope.get().getCorrelatedSlots().containsAll(inputSlots)) {
                     if (currentPlan instanceof LogicalJoin) {
                         throw new AnalysisException(
                                 "Unsupported correlated subquery with 
correlated slot in join conjuncts "
                                         + currentPlan);
                     }
-                    outerScope.get().getCorrelatedSlots().add((Slot) 
firstBound);
+                    outerScope.get().getCorrelatedSlots().addAll(inputSlots);
                 }
                 if (firstBound.getDataType() instanceof NestedColumnPrunable
                         || firstBound.getDataType().isVariantType()) {
@@ -419,14 +446,27 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
                 .map(ArrayItemReference::toSlot)
                 .collect(ImmutableList.toImmutableList());
 
+        ExpressionAnalyzer enclosingAnalyzer = this;
         ExpressionAnalyzer lambdaAnalyzer = new 
ExpressionAnalyzer(currentPlan, new Scope(Optional.of(getScope()),
                 boundedSlots), context == null ? null : 
context.cascadesContext,
                 true, true) {
             @Override
-            protected void couldNotFoundColumn(UnboundSlot unboundSlot, String 
tableName) {
-                throw new AnalysisException("Unknown lambda slot '"
-                        + 
unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
-                        + " in lambda arguments" + 
lambda.getLambdaArgumentNames());
+            public Expression visitUnboundSlot(UnboundSlot unboundSlot, 
ExpressionRewriteContext context) {
+                // The lambda arguments are the nearest lexical scope. Every 
other name is resolved by the
+                // enclosing analyzer rather than by its default scope, 
because ORDER BY, HAVING and QUALIFY
+                // layer several local scopes and a correlated subquery sees 
its outer scope:
+                //   select id from t order by array_sum(array_map(x -> x + v, 
arr))  -- v is not in the output
+                if (bindSlotByThisScope(unboundSlot).isEmpty()) {
+                    return enclosingAnalyzer.visitUnboundSlot(unboundSlot, 
context);
+                }
+                return super.visitUnboundSlot(unboundSlot, context);
+            }
+
+            @Override
+            protected boolean shouldPrioritizeRelationQualifier() {
+                // a name that starts with a lambda argument is a field of it, 
even if a relation of the
+                // enclosing scope has the same name: array_map(x -> x.value, 
x.items) from t x
+                return false;
             }
         };
         lambdaFunction = lambdaAnalyzer.analyze(lambdaFunction, context);
@@ -437,6 +477,11 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
         return unboundFunction.withChildren(ImmutableList.of(lambdaClosure));
     }
 
+    /** Whether relation-qualified columns should be resolved across scopes 
before nested fields. */
+    protected boolean shouldPrioritizeRelationQualifier() {
+        return true;
+    }
+
     UnboundFunction preProcessUnboundFunction(UnboundFunction unboundFunction, 
ExpressionRewriteContext context) {
         if (unboundFunction.isHighOrder()) {
             unboundFunction = processHighOrderFunction(unboundFunction, 
context);
@@ -1031,17 +1076,27 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
         return bindSlotByScope(unboundSlot, getScope());
     }
 
+    protected SlotBinding bindSlotByRelationQualifierInThisScope(UnboundSlot 
unboundSlot) {
+        return bindSlotByRelationQualifier(unboundSlot, getScope());
+    }
+
     protected List<Expression> bindExactSlotsByThisScope(UnboundSlot 
unboundSlot, Scope scope) {
-        List<Expression> candidates = bindSlotByScope(unboundSlot, scope);
+        return bindExactSlotsByThisScope(unboundSlot, scope, 
false).getBoundSlots();
+    }
+
+    protected SlotBinding bindExactSlotsByThisScope(
+            UnboundSlot unboundSlot, Scope scope, boolean 
bindRelationQualifierOnly) {
+        SlotBinding binding = bindSlotByScope(unboundSlot, scope, 
bindRelationQualifierOnly);
+        List<Expression> candidates = binding.getBoundSlots();
         if (candidates.size() == 1) {
-            return candidates;
+            return binding;
         }
         List<Expression> extractSlots = Utils.filterImmutableList(candidates, 
bound ->
                 bound instanceof Slot && unboundSlot.getNameParts().size() == 
((Slot) bound).getQualifier().size() + 1
         );
         // we should return origin candidates slots if extract slots is empty,
         // and then throw an ambiguous exception
-        return !extractSlots.isEmpty() ? extractSlots : candidates;
+        return binding.withBoundSlots(!extractSlots.isEmpty() ? extractSlots : 
candidates);
     }
 
     private List<Slot> addSqlIndexInfo(List<Slot> slots, 
Optional<Pair<Integer, Integer>> indexInSql) {
@@ -1080,12 +1135,128 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
         }
     }
 
+    protected SlotBinding bindSlotByScope(
+            UnboundSlot unboundSlot, Scope scope, boolean 
bindRelationQualifierOnly) {
+        return bindRelationQualifierOnly
+                ? bindSlotByRelationQualifier(unboundSlot, scope)
+                : new SlotBinding(bindSlotByScope(unboundSlot, scope), false);
+    }
+
+    /** Bind a multipart slot as a relation-qualified column, without treating 
its first part as a column. */
+    protected SlotBinding bindSlotByRelationQualifier(UnboundSlot unboundSlot, 
Scope scope) {
+        List<? extends Expression> bounded = 
bindSlotsByRelationQualifier(unboundSlot, scope);
+        return bounded.isEmpty()
+                ? new SlotBinding(bounded,
+                        () -> 
containsRelationQualifier(unboundSlot.getNameParts(), scope))
+                : new SlotBinding(bounded, false);
+    }
+
+    private List<? extends Expression> 
bindSlotsByRelationQualifier(UnboundSlot unboundSlot, Scope scope) {
+        List<String> nameParts = unboundSlot.getNameParts();
+        Optional<Pair<Integer, Integer>> idxInSql = 
unboundSlot.getIndexInSqlString();
+        List<? extends Expression> bounded;
+        switch (nameParts.size()) {
+            case 1:
+                bounded = ImmutableList.of();
+                break;
+            case 2:
+                bounded = bindExpressionByTableColumn(
+                        unboundSlot, nameParts, idxInSql, scope, false);
+                break;
+            case 3:
+                bounded = bindExpressionByDbTableColumn(
+                        unboundSlot, nameParts, idxInSql, scope, false);
+                break;
+            default:
+                bounded = bindExpressionByCatalogDbTableColumn(
+                        unboundSlot, nameParts, idxInSql, scope, false);
+                break;
+        }
+        return bounded;
+    }
+
+    private boolean containsRelationQualifier(List<String> nameParts, Scope 
scope) {
+        int lastRelationNameIndex = Math.min(2, nameParts.size() - 2);
+        for (int relationNameIndex = 0; relationNameIndex <= 
lastRelationNameIndex; relationNameIndex++) {
+            for (List<String> qualifier
+                    : 
scope.findRelationQualifiersIgnoreCase(nameParts.get(relationNameIndex))) {
+                String catalogName = extractCatalogName(qualifier);
+                int lowerCaseTableNames = 
resolveLowerCaseTableNames(catalogName);
+                int lowerCaseDatabaseNames = 
resolveLowerCaseDatabaseNames(catalogName);
+                if (nameParts.size() >= 4 && qualifier.size() >= 3
+                        && qualifier.get(qualifier.size() - 
3).equalsIgnoreCase(nameParts.get(0))
+                        && 
compareDbNameIgnoreClusterName(qualifier.get(qualifier.size() - 2),
+                                nameParts.get(1), lowerCaseDatabaseNames)
+                        && sameTableName(qualifier.get(qualifier.size() - 1),
+                                nameParts.get(2), lowerCaseTableNames)) {
+                    return true;
+                }
+                if (nameParts.size() >= 3 && qualifier.size() >= 2
+                        && 
compareDbNameIgnoreClusterName(qualifier.get(qualifier.size() - 2),
+                                nameParts.get(0), lowerCaseDatabaseNames)
+                        && sameTableName(qualifier.get(qualifier.size() - 1),
+                                nameParts.get(1), lowerCaseTableNames)) {
+                    return true;
+                }
+                if (sameTableName(qualifier.get(qualifier.size() - 1),
+                        nameParts.get(0), lowerCaseTableNames)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Relation-qualified binding candidates and whether that qualifier 
exists in the searched scope. */
+    protected static class SlotBinding {
+        private final List<Expression> boundSlots;
+        private final Supplier<Boolean> relationQualifierOccupied;
+
+        protected SlotBinding(List<? extends Expression> boundSlots, boolean 
relationQualifierOccupied) {
+            this(boundSlots, () -> relationQualifierOccupied);
+        }
+
+        private SlotBinding(List<? extends Expression> boundSlots, 
Supplier<Boolean> relationQualifierOccupied) {
+            this.boundSlots = ImmutableList.copyOf(boundSlots);
+            this.relationQualifierOccupied = relationQualifierOccupied;
+        }
+
+        protected List<Expression> getBoundSlots() {
+            return boundSlots;
+        }
+
+        protected boolean isRelationQualifierOccupied() {
+            return relationQualifierOccupied.get();
+        }
+
+        protected SlotBinding firstOrEmpty() {
+            return boundSlots.isEmpty()
+                    ? this
+                    : new SlotBinding(ImmutableList.of(boundSlots.get(0)), 
relationQualifierOccupied);
+        }
+
+        private SlotBinding withBoundSlots(List<? extends Expression> 
boundSlots) {
+            return new SlotBinding(boundSlots, relationQualifierOccupied);
+        }
+
+        protected SlotBinding withQualifierOccupancyFrom(SlotBinding other) {
+            return new SlotBinding(boundSlots,
+                    () -> relationQualifierOccupied.get() || 
other.relationQualifierOccupied.get());
+        }
+    }
+
     private List<? extends Expression> bindExpressionByCatalogDbTableColumn(
             UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql, Scope scope) {
+        return bindExpressionByCatalogDbTableColumn(unboundSlot, nameParts, 
idxInSql, scope, true);
+    }
+
+    private List<? extends Expression> bindExpressionByCatalogDbTableColumn(
+            UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql,
+            Scope scope, boolean fallbackToColumn) {
         List<Slot> slots = bindSingleSlotByCatalog(
                         nameParts.get(0), nameParts.get(1), nameParts.get(2), 
nameParts.get(3), scope);
         if (slots.isEmpty()) {
-            return bindExpressionByDbTableColumn(unboundSlot, nameParts, 
idxInSql, scope);
+            return bindExpressionByDbTableColumn(unboundSlot, nameParts, 
idxInSql, scope, fallbackToColumn);
         } else if (slots.size() > 1) {
             return addSqlIndexInfo(slots, idxInSql);
         }
@@ -1104,9 +1275,15 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
 
     private List<? extends Expression> bindExpressionByDbTableColumn(
             UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql, Scope scope) {
+        return bindExpressionByDbTableColumn(unboundSlot, nameParts, idxInSql, 
scope, true);
+    }
+
+    private List<? extends Expression> bindExpressionByDbTableColumn(
+            UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql,
+            Scope scope, boolean fallbackToColumn) {
         List<Slot> slots = bindSingleSlotByDb(nameParts.get(0), 
nameParts.get(1), nameParts.get(2), scope);
         if (slots.isEmpty()) {
-            return bindExpressionByTableColumn(unboundSlot, nameParts, 
idxInSql, scope);
+            return bindExpressionByTableColumn(unboundSlot, nameParts, 
idxInSql, scope, fallbackToColumn);
         } else if (slots.size() > 1) {
             return addSqlIndexInfo(slots, idxInSql);
         }
@@ -1125,9 +1302,17 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
 
     private List<? extends Expression> bindExpressionByTableColumn(
             UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql, Scope scope) {
+        return bindExpressionByTableColumn(unboundSlot, nameParts, idxInSql, 
scope, true);
+    }
+
+    private List<? extends Expression> bindExpressionByTableColumn(
+            UnboundSlot unboundSlot, List<String> nameParts, 
Optional<Pair<Integer, Integer>> idxInSql,
+            Scope scope, boolean fallbackToColumn) {
         List<Slot> slots = bindSingleSlotByTable(nameParts.get(0), 
nameParts.get(1), scope);
         if (slots.isEmpty()) {
-            return bindExpressionByColumn(unboundSlot, nameParts, idxInSql, 
scope);
+            return fallbackToColumn
+                    ? bindExpressionByColumn(unboundSlot, nameParts, idxInSql, 
scope)
+                    : ImmutableList.of();
         } else if (slots.size() > 1) {
             return addSqlIndexInfo(slots, idxInSql);
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoin.java
index 72f1752c375..2d0a032fd9c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoin.java
@@ -23,6 +23,7 @@ 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.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
 import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
@@ -103,6 +104,15 @@ public class ProjectOtherJoinConditionForNestedLoopJoin 
extends OneRewriteRuleFa
     private static class AliasReplacer extends 
DefaultExpressionRewriter<ReplacerContext> {
         public static AliasReplacer INSTANCE = new AliasReplacer();
 
+        @Override
+        public Expression visitLambda(Lambda lambda, ReplacerContext ctx) {
+            // A lambda body is evaluated per array item. An expression in it 
may reference the lambda
+            // arguments, which are not input slots and which no child of the 
join outputs, so it can
+            // not be evaluated in a child Project:
+            //   array_map(x -> x + t2.b, [0]) > t1.a   -- `x + t2.b` must 
stay inside the lambda
+            return lambda;
+        }
+
         @Override
         public Expression visit(Expression expression, ReplacerContext ctx) {
             Set<Slot> input = expression.getInputSlots();
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/ScopeTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/ScopeTest.java
new file mode 100644
index 00000000000..859874bdbe8
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/analyzer/ScopeTest.java
@@ -0,0 +1,48 @@
+// 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.analyzer;
+
+import org.apache.doris.nereids.trees.expressions.ExprId;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.types.IntegerType;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+class ScopeTest {
+
+    @Test
+    void testFindRelationQualifiersIgnoreCase() {
+        List<String> qualifier1 = ImmutableList.of("internal", "db", "t1");
+        List<String> qualifier2 = ImmutableList.of("internal", "db", "t2");
+        Scope scope = new Scope(ImmutableList.of(
+                new SlotReference(new ExprId(1), "c1", IntegerType.INSTANCE, 
true, qualifier1),
+                new SlotReference(new ExprId(2), "c2", IntegerType.INSTANCE, 
true, qualifier1),
+                new SlotReference(new ExprId(3), "c1", IntegerType.INSTANCE, 
true, qualifier2),
+                new SlotReference(new ExprId(4), "unqualified", 
IntegerType.INSTANCE, true, ImmutableList.of())));
+
+        Assertions.assertEquals(ImmutableList.of(qualifier1),
+                
ImmutableList.copyOf(scope.findRelationQualifiersIgnoreCase("T1")));
+        Assertions.assertEquals(ImmutableList.of(qualifier2),
+                
ImmutableList.copyOf(scope.findRelationQualifiersIgnoreCase("t2")));
+        
Assertions.assertTrue(scope.findRelationQualifiersIgnoreCase("unqualified").isEmpty());
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java
index 052325cac35..7cd3e3ef79b 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java
@@ -35,9 +35,60 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 
 public class ExpressionAnalyzerTest {
 
+    @Test
+    void testSkipQualifierOccupancyForLocalBindingHit() {
+        SlotReference localSlot = new SlotReference(
+                new ExprId(1), "c", BigIntType.INSTANCE, true, 
ImmutableList.of("t"));
+        Scope outerScope = new Scope(ImmutableList.of());
+        Scope localScope = new Scope(Optional.of(outerScope), 
ImmutableList.of(localSlot)) {
+            @Override
+            public Set<List<String>> findRelationQualifiersIgnoreCase(String 
relationName) {
+                throw new AssertionError("Qualifier occupancy should not be 
evaluated for a binding hit");
+            }
+        };
+        ExpressionAnalyzer analyzer = new ExpressionAnalyzer(null, localScope, 
null, true, true);
+
+        Assertions.assertEquals(localSlot, analyzer.analyze(new 
UnboundSlot("t", "c")));
+    }
+
+    @Test
+    void testOuterRelationProbeDoesNotEvaluateQualifierOccupancy() {
+        SlotReference outerSlot = new SlotReference(
+                new ExprId(1), "c", BigIntType.INSTANCE, true, 
ImmutableList.of("t"));
+        Scope outerScope = new Scope(ImmutableList.of(outerSlot)) {
+            @Override
+            public Set<List<String>> findRelationQualifiersIgnoreCase(String 
relationName) {
+                throw new AssertionError("Outer relation probe should only 
bind slots");
+            }
+        };
+        Scope localScope = new Scope(Optional.of(outerScope), 
ImmutableList.of());
+        ExpressionAnalyzer analyzer = new ExpressionAnalyzer(null, localScope, 
null, true, true);
+
+        Assertions.assertEquals(outerSlot, analyzer.analyze(new 
UnboundSlot("t", "c")));
+        Assertions.assertEquals(ImmutableList.of(outerSlot), 
ImmutableList.copyOf(outerScope.getCorrelatedSlots()));
+    }
+
+    @Test
+    void testKeepQualifierOccupancyLazyInExactBinding() {
+        Scope scope = new Scope(ImmutableList.of()) {
+            @Override
+            public Set<List<String>> findRelationQualifiersIgnoreCase(String 
relationName) {
+                throw new AssertionError("Exact binding should preserve lazy 
qualifier occupancy");
+            }
+        };
+        ExpressionAnalyzer analyzer = new ExpressionAnalyzer(null, scope, 
null, true, true);
+
+        ExpressionAnalyzer.SlotBinding binding = Assertions.assertDoesNotThrow(
+                () -> analyzer.bindExactSlotsByThisScope(new UnboundSlot("t", 
"c"), scope, true));
+        Assertions.assertTrue(binding.getBoundSlots().isEmpty());
+        Assertions.assertThrows(AssertionError.class, 
binding::isRelationQualifierOccupied);
+    }
+
     @Test
     void testPreProcessUnboundFunctionForThreeArgsDataTimeFunction() {
         ExpressionAnalyzer analyzer = new ExpressionAnalyzer(null, new 
Scope(ImmutableList.of()),
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/TestDereference.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/TestDereference.java
index 4731a4c988b..fd44b4d003e 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/TestDereference.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/TestDereference.java
@@ -17,18 +17,30 @@
 
 package org.apache.doris.nereids.rules.analysis;
 
+import org.apache.doris.catalog.ArrayType;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.PrimitiveType;
+import org.apache.doris.catalog.StructField;
+import org.apache.doris.catalog.StructType;
+import org.apache.doris.catalog.Type;
 import org.apache.doris.catalog.VariantType;
 import org.apache.doris.common.FeConstants;
 import 
org.apache.doris.datasource.test.TestExternalCatalog.TestCatalogProvider;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import 
org.apache.doris.nereids.trees.expressions.ArrayItemReference.ArrayItemSlot;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ElementAt;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalApply;
 import org.apache.doris.nereids.util.PlanChecker;
 import org.apache.doris.utframe.TestWithFeService;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -39,6 +51,32 @@ public class TestDereference extends TestWithFeService {
                     "t", ImmutableList.of(
                             new Column("id", PrimitiveType.INT),
                             new Column("t", new VariantType())
+                    ),
+                    "outer_table", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("value", PrimitiveType.INT),
+                            new Column("@event_name", PrimitiveType.VARCHAR),
+                            new Column("payload", new StructType(new 
StructField("k", Type.INT))),
+                            new Column("items", new ArrayType(
+                                    new StructType(new StructField("value", 
Type.INT))))
+                    ),
+                    "inner_table", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("t1", PrimitiveType.INT),
+                            new Column("t", new StructType(new 
StructField("value", Type.INT)))
+                    ),
+                    "inner_variant_table", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("outer_alias", new VariantType())
+                    ),
+                    "shadow_table", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT),
+                            new Column("v", PrimitiveType.INT),
+                            new Column("s", new StructType(new 
StructField("v", Type.INT))),
+                            new Column("arr", new ArrayType(Type.INT))
+                    ),
+                    "plain_table", ImmutableList.of(
+                            new Column("id", PrimitiveType.INT)
                     )
             )
     );
@@ -70,6 +108,253 @@ public class TestDereference extends TestWithFeService {
         testBind("select t.t.t.t.t.t from t");
     }
 
+    @Test
+    public void testCorrelatedSubqueryPrefersOuterTableAlias() {
+        testBind("select t1.`@event_name` from outer_table t1 where exists ("
+                + "select 1 from inner_table inner_alias where 
t1.`@event_name` = 'click')");
+    }
+
+    @Test
+    public void testOuterTableAliasTakesPriorityOverInnerVariantColumn() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select outer_alias.id from outer_table outer_alias 
where exists ("
+                        + "select 1 from inner_variant_table inner_alias where 
outer_alias.value = 1)")
+                .getPlan();
+
+        LogicalApply<?, ?> apply = getOnlyApply(plan);
+        Assertions.assertEquals(1, apply.getCorrelationSlot().size());
+        Assertions.assertEquals("value", 
apply.getCorrelationSlot().get(0).getName());
+        List<String> qualifier = 
apply.getCorrelationSlot().get(0).getQualifier();
+        Assertions.assertEquals("outer_alias", qualifier.get(qualifier.size() 
- 1));
+    }
+
+    @Test
+    public void testInnerAliasShadowsOuterAliasInFilter() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select t.id from outer_table t where exists ("
+                        + "select 1 from inner_table t where t.id = 1)")
+                .getPlan();
+
+        
Assertions.assertTrue(getOnlyApply(plan).getCorrelationSlot().isEmpty());
+    }
+
+    @Test
+    public void testInnerAliasKeepsNestedFieldFallback() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select t.id from outer_table t where exists ("
+                        + "select 1 from inner_table t where t.value = 1)")
+                .getPlan();
+
+        
Assertions.assertTrue(getOnlyApply(plan).getCorrelationSlot().isEmpty());
+    }
+
+    @Test
+    public void testInnerAliasKeepsScalarFieldError() {
+        AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                () -> PlanChecker.from(connectContext)
+                        .analyze("select t1.id from outer_table t1 where 
exists ("
+                                + "select 1 from inner_table t1 where 
t1.`@event_name` = 'click')"));
+        Assertions.assertTrue(exception.getMessage().contains("No such field 
'@event_name' in 't1'"));
+    }
+
+    @Test
+    public void testLambdaArgumentTakesPriorityOverOuterTableAlias() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select array_map(x -> x.value, x.items) from 
outer_table x")
+                .getPlan();
+
+        List<Lambda> lambdas = new ArrayList<>();
+        for (Plan node : plan.<Plan>collectToList(ignored -> true)) {
+            node.getExpressions().forEach(expression ->
+                    
lambdas.addAll(expression.collectToList(Lambda.class::isInstance)));
+        }
+        Assertions.assertEquals(1, lambdas.size());
+        
Assertions.assertTrue(lambdas.get(0).getLambdaFunction().containsType(ElementAt.class));
+        
Assertions.assertTrue(lambdas.get(0).getLambdaFunction().anyMatch(ArrayItemSlot.class::isInstance));
+    }
+
+    @Test
+    public void testOuterNestedFieldRegistersCorrelationSlot() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select outer_alias.id from outer_table outer_alias 
where exists ("
+                        + "select 1 from inner_variant_table inner_alias where 
outer_alias.payload.k = 1)")
+                .getPlan();
+
+        LogicalApply<?, ?> apply = getOnlyApply(plan);
+        Assertions.assertEquals(1, apply.getCorrelationSlot().size());
+        Assertions.assertEquals("payload", 
apply.getCorrelationSlot().get(0).getName());
+        List<String> qualifier = 
apply.getCorrelationSlot().get(0).getQualifier();
+        Assertions.assertEquals("outer_alias", qualifier.get(qualifier.size() 
- 1));
+    }
+
+    @Test
+    public void testInnerHavingAliasShadowsOuterAlias() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select t.id from outer_table t where exists ("
+                        + "select 1 from inner_table t having max(t.id) > 0)")
+                .getPlan();
+
+        
Assertions.assertTrue(getOnlyApply(plan).getCorrelationSlot().isEmpty());
+    }
+
+    @Test
+    public void testInnerQualifyAliasShadowsOuterAlias() {
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select t.id from outer_table t where exists ("
+                        + "select 1 from inner_table t group by t.id "
+                        + "qualify row_number() over (order by id) = t.id)")
+                .getPlan();
+
+        
Assertions.assertTrue(getOnlyApply(plan).getCorrelationSlot().isEmpty());
+    }
+
+    @Test
+    public void testOutputAliasDoesNotShadowRelationQualifier() {
+        // the select output is a nearer scope than the relation for ORDER BY, 
HAVING and QUALIFY,
+        // q.v should still be the column v of relation q rather than a field 
of the scalar output alias q
+        List<String> sqls = ImmutableList.of(
+                "select q.v as q from (select 7 as v) q order by q.v",
+                "select q.v as q from shadow_table q order by q.v",
+                "select distinct q.v as q from shadow_table q order by q.v",
+                "select q.v as p, p.id as q from shadow_table q join 
plain_table p on q.id = p.id order by q.v, p.id",
+                "select * from plain_table o where o.id in (select q.v as q 
from shadow_table q order by q.v limit 1)",
+                // db.table.column, the alias has the same name as the database
+                "select q.v as t from shadow_table q order by t.q.v",
+                // aggregate
+                "select q.id as q from shadow_table q group by q.id order by 
max(q.v)",
+                "select max(q.v) as q from shadow_table q group by q.id order 
by q.id",
+                "select q.id + 1 as q from shadow_table q group by q.id + 1 
order by q.id + 1",
+                "select q.v as q from shadow_table q having q.v > 0",
+                "select q.id + 1 as q from shadow_table q group by q.id + 1 
having q.id + 1 > 0",
+                "select max(q.id) as q from shadow_table q group by q.v having 
q.v > 0",
+                "select q.v as q from shadow_table q qualify row_number() over 
(order by q.id) = 1 and q.v > 0",
+                "select q.id + 1 as q from shadow_table q group by q.id + 1 "
+                        + "qualify row_number() over (order by q.id + 1) = 1"
+        );
+        for (String sql : sqls) {
+            Assertions.assertDoesNotThrow(() -> 
PlanChecker.from(connectContext).analyze(sql), sql);
+        }
+    }
+
+    @Test
+    public void testRelationQualifierTakesPriorityOverComplexOutputAlias() {
+        // the output alias q is a struct with field v, q.v should not 
silently become element_at(q, 'v')
+        assertBoundToColumnV("select q.s as q from shadow_table q order by 
q.v");
+        assertBoundToColumnV("select q.s as q from shadow_table q having q.v > 
0");
+        assertBoundToColumnV("select q.s as q from shadow_table q qualify 
row_number() over (order by q.v) = 1");
+    }
+
+    @Test
+    public void testOutputAliasWithoutRelationQualifierKeepsScalarFieldError() 
{
+        // q is only an output alias here, the relation is p, so q.v is a 
field of the scalar alias q
+        AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                () -> PlanChecker.from(connectContext)
+                        .analyze("select p.v as q from shadow_table p order by 
q.v"));
+        Assertions.assertTrue(exception.getMessage().contains("No such field 
'v' in 'q'"),
+                exception.getMessage());
+    }
+
+    @Test
+    public void testRelationQualifierOccupiesNestedOutputAliasPath() {
+        // q.v is the scalar column v of relation q, so q.v.b is not the path 
v.b of the struct alias q
+        AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                () -> PlanChecker.from(connectContext)
+                        .analyze("select named_struct('v', named_struct('b', 
1)) as q "
+                                + "from shadow_table q order by q.v.b"));
+        Assertions.assertTrue(exception.getMessage().contains("No such field 
'b' in 'v'"),
+                exception.getMessage());
+    }
+
+    @Test
+    public void testOutputAliasKeepsNestedFieldFallback() {
+        // no relation-qualified column matches, so the first part falls back 
to the output alias
+        assertBoundToNestedField("select q.s as a from shadow_table q order by 
a.v");
+        assertBoundToNestedField("select p.s as q from shadow_table p join 
plain_table q on p.id = q.id order by q.v");
+        assertBoundToNestedField("select p.s as q from shadow_table p join 
plain_table q on p.id = q.id "
+                + "having q.v > 0");
+    }
+
+    @Test
+    public void testLambdaBodyBindsByEnclosingClauseScopes() {
+        // a name that is not a lambda argument is resolved the same way as 
outside the lambda,
+        // ORDER BY, HAVING and QUALIFY can see the child output behind the 
select output
+        List<String> sqls = ImmutableList.of(
+                "select id from shadow_table order by array_sum(array_map(x -> 
x + v, arr))",
+                "select id from shadow_table q order by array_sum(array_map(x 
-> x + q.v, q.arr))",
+                "select q.v as q from shadow_table q order by 
array_sum(array_map(x -> x + q.v, q.arr))",
+                "select q.v as q from shadow_table q having 
array_sum(array_map(x -> x + q.v, q.arr)) > 0",
+                "select q.id as q from shadow_table q group by q.id "
+                        + "having sum(array_sum(array_map(x -> x + q.v, 
q.arr))) > 0",
+                "select q.v as q from shadow_table q "
+                        + "qualify row_number() over (order by 
array_sum(array_map(x -> x + q.v, q.arr))) = 1"
+        );
+        for (String sql : sqls) {
+            Assertions.assertDoesNotThrow(() -> 
PlanChecker.from(connectContext).analyze(sql), sql);
+        }
+
+        // a nested lambda resolves through the lambda around it
+        Assertions.assertDoesNotThrow(() -> 
PlanChecker.from(connectContext).analyze(
+                "select id from shadow_table q order by "
+                        + "array_sum(array_map(x -> array_sum(array_map(y -> y 
+ x + q.v, q.arr)), q.arr))"));
+        // the enclosing clause is a join condition, both sides are its own 
scope rather than an outer scope
+        Assertions.assertDoesNotThrow(() -> 
PlanChecker.from(connectContext).analyze(
+                "select q.id from shadow_table q join plain_table p "
+                        + "on array_sum(array_map(x -> x + p.id, q.arr)) > 
0"));
+
+        AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                () -> PlanChecker.from(connectContext)
+                        .analyze("select id from shadow_table order by 
array_map(x -> x + unknown_column, arr)"));
+        Assertions.assertTrue(exception.getMessage().contains("Unknown column 
'unknown_column'"),
+                exception.getMessage());
+    }
+
+    @Test
+    public void testLambdaBodyFollowsAmbiguityOfEnclosingClause() {
+        // id is both the output alias of q.id and the output slot p.id, 
HAVING does not pick the exact match
+        String having = "select q.id as id, p.id from shadow_table q join 
plain_table p on q.id = p.id having ";
+        for (String predicate : ImmutableList.of("id > 0", 
"array_sum(array_map(x -> x + id, q.arr)) > 0")) {
+            AnalysisException exception = 
Assertions.assertThrows(AnalysisException.class,
+                    () -> PlanChecker.from(connectContext).analyze(having + 
predicate), predicate);
+            Assertions.assertTrue(exception.getMessage().contains("id is 
ambiguous"), exception.getMessage());
+        }
+    }
+
+    @Test
+    public void testLambdaBodyRegistersCorrelationSlot() {
+        // the enclosing analyzer of the subquery filter sees the outer scope, 
so does the lambda body
+        Plan plan = PlanChecker.from(connectContext)
+                .analyze("select o.id from plain_table o where exists ("
+                        + "select 1 from shadow_table q where 
array_sum(array_map(x -> x + o.id, q.arr)) > 0)")
+                .getPlan();
+
+        LogicalApply<?, ?> apply = getOnlyApply(plan);
+        Assertions.assertEquals(1, apply.getCorrelationSlot().size());
+        Assertions.assertEquals("id", 
apply.getCorrelationSlot().get(0).getName());
+        List<String> qualifier = 
apply.getCorrelationSlot().get(0).getQualifier();
+        Assertions.assertEquals("o", qualifier.get(qualifier.size() - 1));
+    }
+
+    private void assertBoundToColumnV(String sql) {
+        Plan plan = PlanChecker.from(connectContext).analyze(sql).getPlan();
+        Assertions.assertFalse(containsElementAt(plan), sql);
+    }
+
+    private void assertBoundToNestedField(String sql) {
+        Plan plan = PlanChecker.from(connectContext).analyze(sql).getPlan();
+        Assertions.assertTrue(containsElementAt(plan), sql);
+    }
+
+    private boolean containsElementAt(Plan plan) {
+        return plan.anyMatch(node -> ((Plan) node).getExpressions().stream()
+                .anyMatch(expression -> 
expression.containsType(ElementAt.class)));
+    }
+
+    private LogicalApply<?, ?> getOnlyApply(Plan plan) {
+        List<LogicalApply<?, ?>> applies = 
plan.collectToList(LogicalApply.class::isInstance);
+        Assertions.assertEquals(1, applies.size());
+        return applies.get(0);
+    }
+
     private void testBind(String sql) {
         PlanChecker.from(connectContext)
                 .analyze(sql)
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoinTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoinTest.java
index 960500c75f3..ee1ecef9bc7 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoinTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ProjectOtherJoinConditionForNestedLoopJoinTest.java
@@ -18,10 +18,16 @@
 package org.apache.doris.nereids.rules.rewrite;
 
 import org.apache.doris.nereids.trees.expressions.Add;
+import org.apache.doris.nereids.trees.expressions.ArrayItemReference;
 import org.apache.doris.nereids.trees.expressions.EqualTo;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.LessThan;
 import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayMap;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.ArraySum;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Lambda;
+import org.apache.doris.nereids.trees.expressions.literal.ArrayLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
 import org.apache.doris.nereids.trees.plans.JoinType;
 import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
@@ -32,13 +38,23 @@ import org.apache.doris.nereids.util.PlanChecker;
 import org.apache.doris.nereids.util.PlanConstructor;
 import org.apache.doris.qe.ConnectContext;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 public class ProjectOtherJoinConditionForNestedLoopJoinTest implements 
MemoPatternMatchSupported {
     private final LogicalOlapScan scan1 = 
PlanConstructor.newLogicalOlapScan(0, "t1", 0);
     private final LogicalOlapScan scan2 = 
PlanConstructor.newLogicalOlapScan(1, "t2", 0);
 
+    @AfterEach
+    public void tearDown() {
+        // the scans of the next test should not take their slot ids from the 
statement scope of this test,
+        // they would collide with the ids of the aliases the rule creates
+        ConnectContext.remove();
+    }
+
     @Test
     public void testNestedLoopJoin() {
         Slot a = scan1.getOutput().get(1);
@@ -59,6 +75,46 @@ public class ProjectOtherJoinConditionForNestedLoopJoinTest 
implements MemoPatte
                 ).printlnTree();
     }
 
+    @Test
+    public void testLambdaBodyIsNotProjected() {
+        // t1.id < array_sum(array_map(x -> x + t2.id, [0]))
+        // all input slots of the higher order function come from t2, it is 
projected as a whole with its lambda
+        Slot a = scan1.getOutput().get(0);
+        Slot b = scan2.getOutput().get(0);
+        ArrayItemReference item = new ArrayItemReference("x",
+                new ArrayLiteral(ImmutableList.of(new IntegerLiteral(0))));
+        Lambda lambda = new Lambda(ImmutableList.of("x"), new 
Add(item.toSlot(), b), ImmutableList.of(item));
+        Expression otherCondition = new LessThan(a, new ArraySum(new 
ArrayMap(lambda)));
+
+        LogicalPlan join = new LogicalPlanBuilder(scan1).join(scan2, 
JoinType.CROSS_JOIN,
+                Lists.newArrayList(), 
Lists.newArrayList(otherCondition)).build();
+        PlanChecker.from(MemoTestUtils.createConnectContext(), join)
+                .applyTopDown(new ProjectOtherJoinConditionForNestedLoopJoin())
+                .matchesFromRoot(
+                        logicalJoin(
+                                logicalOlapScan(),
+                                // proj list: id, name, array_sum(array_map(x 
-> x + id, [0])) AS alias
+                                logicalProject().when(proj -> 
proj.getProjects().size() == 3
+                                        && 
proj.getProjects().get(2).containsType(Lambda.class))
+                        ).when(j -> j.getOtherJoinConjuncts().stream()
+                                .noneMatch(conjunct -> 
conjunct.containsType(Lambda.class)))
+                );
+
+        // x + t1.id + t2.id references both sides, the lambda stays in the 
join condition as a whole.
+        // `x + t1.id` has only t1.id as input slot, but x is a lambda 
argument that no child outputs,
+        // so it can not be projected to the left child
+        Lambda mixed = new Lambda(ImmutableList.of("x"), new Add(new 
Add(item.toSlot(), a), b),
+                ImmutableList.of(item));
+        Expression mixedCondition = new LessThan(a, new ArraySum(new 
ArrayMap(mixed)));
+        LogicalPlan mixedJoin = new LogicalPlanBuilder(scan1).join(scan2, 
JoinType.CROSS_JOIN,
+                Lists.newArrayList(), 
Lists.newArrayList(mixedCondition)).build();
+        LogicalPlan rewritten = (LogicalPlan) 
PlanChecker.from(MemoTestUtils.createConnectContext(), mixedJoin)
+                .applyTopDown(new ProjectOtherJoinConditionForNestedLoopJoin())
+                .getPlan();
+        Assertions.assertTrue(rewritten.child(0) instanceof LogicalOlapScan);
+        Assertions.assertTrue(rewritten.child(1) instanceof LogicalOlapScan);
+    }
+
     @Test
     public void testHashJoin() {
         Slot id1 = scan1.getOutput().get(0);
diff --git a/regression-test/data/query_p0/test_dereference.out 
b/regression-test/data/query_p0/test_dereference.out
new file mode 100644
index 00000000000..039f73c3a5c
--- /dev/null
+++ b/regression-test/data/query_p0/test_dereference.out
@@ -0,0 +1,99 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !correlated_scalar_alias --
+2      kept
+
+-- !correlated_complex_alias --
+2      20
+
+-- !correlated_db_table_qualifier --
+2
+
+-- !correlated_catalog_db_table_qualifier --
+1
+
+-- !lambda_alias --
+1      [1, 2]
+2      [3]
+
+-- !nested_correlation --
+1
+
+-- !having_inner_alias --
+1
+2
+
+-- !qualify_inner_alias --
+1
+2
+
+-- !filter_inner_alias --
+1
+2
+
+-- !filter_inner_nested_field --
+1
+2
+
+-- !group_by_inner_nested_field --
+1
+2
+
+-- !alias_shadow_order_by_subquery_alias --
+7
+
+-- !alias_shadow_order_by --
+10
+20
+30
+
+-- !alias_shadow_having --
+20
+30
+
+-- !alias_shadow_order_by_agg_func --
+3
+2
+1
+
+-- !alias_shadow_order_by_over_agg --
+30
+20
+10
+
+-- !alias_shadow_having_group_by_expr --
+4
+
+-- !alias_shadow_qualify_group_by_expr --
+2
+
+-- !alias_shadow_struct_alias_order_by --
+3
+
+-- !alias_shadow_struct_alias_having --
+1
+
+-- !alias_shadow_keep_alias_field --
+3
+
+-- !alias_shadow_lambda_order_by --
+3
+2
+1
+
+-- !alias_shadow_lambda_having --
+20
+30
+
+-- !alias_shadow_lambda_join_on --
+1      1
+1      2
+2      1
+
+-- !alias_shadow_lambda_correlated_exists --
+1
+2
+
+-- !alias_shadow_lambda_correlated_in --
+1
+2
+
diff --git a/regression-test/suites/query_p0/test_dereference.groovy 
b/regression-test/suites/query_p0/test_dereference.groovy
index 30c123e3c37..20a24747846 100644
--- a/regression-test/suites/query_p0/test_dereference.groovy
+++ b/regression-test/suites/query_p0/test_dereference.groovy
@@ -66,4 +66,295 @@ suite("test_dereference") {
         sql "select s.a from test_dereference2"
         exception "No such struct field 'a' in 's'"
     }
-}
\ No newline at end of file
+
+    multi_sql """
+        drop table if exists test_correlated_dereference_outer;
+        drop table if exists test_correlated_dereference_inner_scalar;
+        drop table if exists test_correlated_dereference_inner_struct;
+        create table test_correlated_dereference_outer(
+          id int,
+          value int,
+          `@event_name` varchar(32),
+          payload struct<k:int>,
+          items array<struct<value:int>>
+        )
+        distributed by hash(id)
+        properties('replication_num'='1');
+
+        create table test_correlated_dereference_inner_scalar(
+          id int,
+          t1 int,
+          t struct<value:int>,
+          `${context.dbName}` 
struct<test_correlated_dereference_outer:struct<value:int>>,
+          internal 
struct<`${context.dbName}`:struct<test_correlated_dereference_outer:struct<value:int>>>
+        )
+        distributed by hash(id)
+        properties('replication_num'='1');
+
+        create table test_correlated_dereference_inner_struct(
+          id int,
+          outer_alias struct<value:int>
+        )
+        distributed by hash(id)
+        properties('replication_num'='1');
+
+        insert into test_correlated_dereference_outer values
+            (1, 10, 'blocked', struct(1), array(struct(1), struct(2))),
+            (2, 20, 'kept', struct(2), array(struct(3)));
+        insert into test_correlated_dereference_inner_scalar values
+            (1, 0, struct(1), struct(struct(0)), struct(struct(struct(0)))),
+            (1, 0, struct(2), struct(struct(0)), struct(struct(struct(0))));
+        insert into test_correlated_dereference_inner_struct values (1, 
struct(10));
+        """
+
+    order_qt_correlated_scalar_alias """
+            select t1.id, t1.`@event_name`
+            from test_correlated_dereference_outer t1
+            where not exists (
+                select 1 from test_correlated_dereference_inner_scalar 
inner_alias
+                where t1.`@event_name` = 'blocked'
+            )
+            order by t1.id
+            """
+
+    order_qt_correlated_complex_alias """
+            select outer_alias.id, outer_alias.value
+            from test_correlated_dereference_outer outer_alias
+            where not exists (
+                select 1 from test_correlated_dereference_inner_struct 
inner_alias
+                where outer_alias.value = 10
+            )
+            order by outer_alias.id
+            """
+
+    order_qt_correlated_db_table_qualifier """
+            select test_correlated_dereference_outer.id
+            from test_correlated_dereference_outer
+            where not exists (
+                select 1 from test_correlated_dereference_inner_scalar 
inner_alias
+                where 
`${context.dbName}`.`test_correlated_dereference_outer`.value = 10
+            )
+            order by test_correlated_dereference_outer.id
+            """
+
+    order_qt_correlated_catalog_db_table_qualifier """
+            select test_correlated_dereference_outer.id
+            from test_correlated_dereference_outer
+            where not exists (
+                select 1 from test_correlated_dereference_inner_scalar 
inner_alias
+                where 
internal.`${context.dbName}`.`test_correlated_dereference_outer`.value = 20
+            )
+            order by test_correlated_dereference_outer.id
+            """
+
+    order_qt_lambda_alias """
+            select x.id, array_map(x -> x.value, x.items)
+            from test_correlated_dereference_outer x
+            order by x.id
+            """
+
+    order_qt_nested_correlation """
+            select outer_alias.id
+            from test_correlated_dereference_outer outer_alias
+            where exists (
+                select 1 from test_correlated_dereference_inner_struct 
inner_alias
+                where outer_alias.payload.k = 1
+            )
+            order by outer_alias.id
+            """
+
+    order_qt_having_inner_alias """
+            select t.id
+            from test_correlated_dereference_outer t
+            where exists (
+                select 1
+                from test_correlated_dereference_inner_scalar t
+                having max(t.id) < 2
+            )
+            order by t.id
+            """
+
+    order_qt_qualify_inner_alias """
+            select t.id
+            from test_correlated_dereference_outer t
+            where exists (
+                select 1
+                from test_correlated_dereference_inner_scalar t
+                group by t.id
+                qualify row_number() over (order by id) = t.id
+            )
+            order by t.id
+            """
+
+    order_qt_filter_inner_alias """
+            select t.id
+            from test_correlated_dereference_outer t
+            where exists (
+                select 1
+                from test_correlated_dereference_inner_scalar t
+                where t.id = 1
+            )
+            order by t.id
+            """
+
+    order_qt_filter_inner_nested_field """
+            select t.id
+            from test_correlated_dereference_outer t
+            where exists (
+                select 1
+                from test_correlated_dereference_inner_scalar t
+                where t.value = 1
+            )
+            order by t.id
+            """
+
+    order_qt_group_by_inner_nested_field """
+            select t.id
+            from test_correlated_dereference_outer t
+            where not exists (
+                select 1
+                from test_correlated_dereference_inner_scalar t
+                group by t.value
+                having count(*) > 1
+            )
+            order by t.id
+            """
+
+    // An output alias is a nearer scope than the relation for ORDER BY, 
HAVING and QUALIFY.
+    // A relation-qualified column should still bind to the relation when an 
output alias reuses its name.
+    multi_sql """
+        drop table if exists test_dereference_alias_shadow;
+        create table test_dereference_alias_shadow(
+          id int,
+          v int,
+          s struct<v:int>
+        )
+        distributed by hash(id) buckets 1
+        properties(
+          'replication_num'='1'
+        );
+
+        insert into test_dereference_alias_shadow
+        values (1, 30, struct(1)), (2, 20, struct(2)), (3, 10, struct(3));
+        """
+
+    qt_alias_shadow_order_by_subquery_alias "select q.v as q from (select 7 as 
v) q order by q.v"
+
+    qt_alias_shadow_order_by "select q.v as q from 
test_dereference_alias_shadow q order by q.v"
+
+    qt_alias_shadow_having "select q.v as q from test_dereference_alias_shadow 
q having q.v > 15 order by q.v"
+
+    qt_alias_shadow_order_by_agg_func """
+            select q.id as q from test_dereference_alias_shadow q group by 
q.id order by max(q.v)
+            """
+
+    qt_alias_shadow_order_by_over_agg """
+            select max(q.v) as q from test_dereference_alias_shadow q group by 
q.id order by q.id
+            """
+
+    qt_alias_shadow_having_group_by_expr """
+            select q.id + 1 as q from test_dereference_alias_shadow q
+            group by q.id + 1 having q.id + 1 > 3
+            """
+
+    qt_alias_shadow_qualify_group_by_expr """
+            select q.id + 1 as q from test_dereference_alias_shadow q
+            group by q.id + 1 qualify row_number() over (order by q.id + 1) = 1
+            """
+
+    // the output alias q is a struct that has a field v: q.v is still the 
column v of relation q
+    qt_alias_shadow_struct_alias_order_by """
+            select id from (
+                select q.id as id, q.s as q from test_dereference_alias_shadow 
q order by q.v limit 1
+            ) x
+            """
+
+    qt_alias_shadow_struct_alias_having """
+            select id from (
+                select q.id as id, q.s as q from test_dereference_alias_shadow 
q having q.v > 25
+            ) x
+            """
+
+    // no relation-qualified column matches, fall back to the nested field of 
the output alias
+    qt_alias_shadow_keep_alias_field """
+            select id from (
+                select p.id as id, p.s as q from test_dereference_alias_shadow 
p order by q.v desc limit 1
+            ) x
+            """
+
+    // a lambda body resolves names the same way as the clause around it
+    qt_alias_shadow_lambda_order_by """
+            select id from test_dereference_alias_shadow q
+            order by array_sum(array_map(x -> x + q.v, [1]))
+            """
+
+    qt_alias_shadow_lambda_having """
+            select q.v as q from test_dereference_alias_shadow q
+            having array_sum(array_map(x -> x + q.v, [1])) > 16
+            order by array_sum(array_map(x -> x + q.v, [1]))
+            """
+
+    // a lambda body in a join condition references columns of both sides of 
the join
+    qt_alias_shadow_lambda_join_on """
+            select q.id, p.id
+            from test_dereference_alias_shadow q join 
test_dereference_alias_shadow p
+            on array_sum(array_map(x -> x + p.v + q.v, [0])) > 40
+            order by q.id, p.id
+            """
+
+    // a lambda body in a correlated subquery references a column of the outer 
query
+    qt_alias_shadow_lambda_correlated_exists """
+            select o.id from test_dereference_alias_shadow o
+            where exists (
+                select 1 from test_dereference_alias_shadow q
+                where array_sum(array_map(x -> x + o.v + q.v, [0])) > 45
+            )
+            order by o.id
+            """
+
+    qt_alias_shadow_lambda_correlated_in """
+            select o.id from test_dereference_alias_shadow o
+            where o.id in (
+                select q.id from test_dereference_alias_shadow q
+                where array_sum(array_map(x -> x + o.v, [0])) > 15
+            )
+            order by o.id
+            """
+
+    // q is only a scalar output alias here, the relation is p
+    test {
+        sql "select p.v as q from test_dereference_alias_shadow p order by q.v"
+        exception "No such field 'v' in 'q'"
+    }
+
+    // q.v is the scalar column v of relation q, so q.v.b is not the path v.b 
of the struct alias q
+    test {
+        sql """
+            select named_struct('v', named_struct('b', 1)) as q
+            from test_dereference_alias_shadow q order by q.v.b
+            """
+        exception "No such field 'b' in 'v'"
+    }
+
+    // an unknown name in a lambda body reports the error of the clause around 
it
+    test {
+        sql """
+            select id from test_dereference_alias_shadow
+            order by array_sum(array_map(x -> x + unknown_column, [1]))
+            """
+        exception "Unknown column 'unknown_column'"
+    }
+
+    test {
+        sql """
+            select t1.id
+            from test_correlated_dereference_outer t1
+            where exists (
+                select 1
+                from test_correlated_dereference_inner_scalar t1
+                where t1.`@event_name` = 'blocked'
+            )
+            """
+        exception "No such field '@event_name' in 't1'"
+    }
+}


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

Reply via email to