jayzhan211 commented on code in PR #27133:
URL: https://github.com/apache/datafusion/pull/27133#discussion_r4504914462


##########
datafusion/optimizer/src/decorrelate.rs:
##########
@@ -573,10 +573,15 @@ impl TreeNodeRewriter for PullUpCorrelatedExpr {
                     self.can_pull_up = false;
                     return Ok(Transformed::new(plan, false, 
TreeNodeRecursion::Stop));
                 }
-                // If the aggregation is from a distinct it will not change 
the result for
-                // exists/in subqueries so we can still pull up all predicates.
+                // Only a DISTINCT under EXISTS/IN may skip this check. The 
pulled
+                // up column becomes an extra group key, which for a DISTINCT 
only
+                // adds duplicate rows, and EXISTS/IN ignore duplicates. 
Elsewhere,
+                // e.g. LATERAL, the duplicates reach the result. See #25808.
                 let is_distinct = aggregate.aggr_expr.is_empty();
-                if !is_distinct {
+                let is_exists_or_in =
+                    self.exists_sub_query || self.in_predicate_opt.is_some();
+                let can_skip_check = is_distinct && is_exists_or_in;
+                if !can_skip_check {
                     self.can_pull_up = self.can_pull_up && 
self.can_pull_over_aggregation;
                 }

Review Comment:
   This also turns LATERAL queries that are correct today into errors. When the 
correlated column is already a DISTINCT key, the pulled up filter reads only 
group keys, so it works like a HAVING and adds no duplicates. For example, 
`SELECT DISTINCT t2.id FROM t2 WHERE t2.id > t1.id * 10` returns `(1,20) (1,30) 
(2,30)` on main and on 55.2.0, and an `OuterReferenceColumn` error here. The 
change below doesn't decorrelate anything main doesn't already handle 
correctly. It only skips the check when the pull up adds no group key:
   
   ```diff
   -                // Only a DISTINCT under EXISTS/IN may skip this check. The 
pulled
   -                // up column becomes an extra group key, which for a 
DISTINCT only
   -                // adds duplicate rows, and EXISTS/IN ignore duplicates. 
Elsewhere,
   -                // e.g. LATERAL, the duplicates reach the result. See 
#25808.
   -                let is_distinct = aggregate.aggr_expr.is_empty();
   -                let is_exists_or_in =
   -                    self.exists_sub_query || 
self.in_predicate_opt.is_some();
   -                let can_skip_check = is_distinct && is_exists_or_in;
   -                if !can_skip_check {
   -                    self.can_pull_up = self.can_pull_up && 
self.can_pull_over_aggregation;
   -                }
                    let mut local_correlated_cols = BTreeSet::new();
   @@
                        )?
                    };
    
   +                // A DISTINCT may skip this check when the pull up adds no 
group
   +                // key, or under EXISTS/IN. An added key only adds 
duplicate rows,
   +                // which EXISTS/IN ignore. Elsewhere, e.g. LATERAL, the 
duplicates
   +                // reach the result. See #25808.
   +                let is_distinct = aggregate.aggr_expr.is_empty();
   +                let is_exists_or_in =
   +                    self.exists_sub_query || 
self.in_predicate_opt.is_some();
   +                let adds_group_keys = missing_exprs
   +                    .iter()
   +                    .any(|expr| !aggregate.group_expr.contains(expr));
   +                let can_skip_check = is_distinct && (is_exists_or_in || 
!adds_group_keys);
   +                if !can_skip_check {
   +                    self.can_pull_up = self.can_pull_up && 
self.can_pull_over_aggregation;
   +                }
   +
                    // if the original group expressions are empty, need to 
handle the Count bug
   ```
   
   <details>
   <summary>Per-query results on 55.2.0, main, this PR, and with the change; 
test for lateral_join.slt</summary>
   
   | LATERAL subquery (non-equality filter) | 55.2.0 and main | this PR | with 
the change |
   |---|---|---|---|
   | issue: `DISTINCT x WHERE y > k` | duplicates | error | error |
   | `DISTINCT y WHERE y > k` | correct | error | correct |
   | `DISTINCT x, y WHERE y > k` | correct | error | correct |
   | `LEFT JOIN LATERAL (DISTINCT y WHERE y > k)` | correct | error | correct |
   | `DISTINCT y + 1 WHERE y > k` | correct | error | error |
   
   ```sql
   # A non-equality filter on a column that is already a DISTINCT key adds no
   # key, so the pull up acts as a HAVING and stays decorrelated.
   query II
   SELECT t1.id, sub.id
   FROM t1, LATERAL (SELECT DISTINCT t2.id FROM t2 WHERE t2.id > t1.id * 10) AS 
sub
   ORDER BY t1.id, sub.id;
   ----
   1 20
   1 30
   2 30
   ```
   
   With the change, `lateral_join.slt`, `subquery.slt`, `joins.slt`, the 
optimizer unit tests, and clippy pass, and your new tests still hold. The test 
fails on this PR as is.
   </details>



##########
datafusion/sqllogictest/test_files/lateral_join.slt:
##########
@@ -855,6 +855,27 @@ ORDER BY t1.id, sub.id;
 1 20 2 1
 2 30 1 1
 
+# --- Non-equality correlated filter below a DISTINCT ---
+# https://github.com/apache/datafusion/issues/25808
+# The pull up adds `t2.id` to the DISTINCT keys, so the t2 rows 10 and 20,
+# which both have t1_id = 1, are no longer merged.
+# Expected result once supported: sub.t1_id = 1 and 2 once each, for every
+# t1 row (6 rows).
+statement error OuterReferenceColumn

Review Comment:
   Optional: two more shapes returned duplicates on main and now error, `GROUP 
BY` without aggregates and `LEFT JOIN LATERAL`. A `statement error` for each 
would pin them:
   
   ```sql
   statement error OuterReferenceColumn
   SELECT t1.id, sub.t1_id
   FROM t1, LATERAL (SELECT t2.t1_id FROM t2 WHERE t2.id > t1.id GROUP BY 
t2.t1_id) AS sub;
   
   statement error OuterReferenceColumn
   SELECT t1.id, sub.t1_id
   FROM t1 LEFT JOIN LATERAL (SELECT DISTINCT t2.t1_id FROM t2 WHERE t2.id > 
t1.id) AS sub ON true;
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to