Repository navigation
Conversation
|
Checked this against DuckDB 1.5.6 with a few more shapes (x(a) = 1..4, y(a, b) = (1,10), (1,5), (3,NULL), (4,7)):
So nothing that worked before is rejected now. Looks good to me. |
|
Thanks for checking these against DuckDB, @raghavyadav01! I added the |
jayzhan211
left a comment
There was a problem hiding this comment.
@abp002 thanks for the fix! In scalar_subquery_to_join.rs around line 403, the count(*) path this PR keeps decorrelated still ignores a HAVING that is NULL for a matched group: the query below returns 1 1, but should return 1 NULL. The arm is WHEN NOT(having) THEN NULL, and NOT NULL falls through to ELSE. Since this is #26505's shape, could you fix it here with IS NOT TRUE and add the query as a test? With that change it returns 1 NULL, and only two EXPLAIN lines in subquery.slt change (!= Int64(0) becomes = Int64(0) IS NOT TRUE).
SELECT x.a, (SELECT count(*) FROM (VALUES (1, CAST(NULL AS INT))) AS y(a, b)
WHERE y.a = x.a HAVING count(*) = 0 OR sum(y.b) > 5) AS v
FROM (VALUES (1), (2)) AS x(a) ORDER BY x.a;
-- expected: 1 NULL, 2 0- .map(|f| (not(f.clone()), lit(ScalarValue::Null)));
+ .map(|f| (f.clone().is_not_true(), lit(ScalarValue::Null)));| // below, which skips the expressions that are NULL on empty input. Keep | ||
| // the subquery correlated rather than drop the `HAVING` for its output. | ||
| // <https://github.com/apache/datafusion/issues/26505> | ||
| if pull_up.pull_up_having_expr.is_some() { |
There was a problem hiding this comment.
Since #26723 merged, main already computes this lookup in build_join as output_needs_compensation, so after a rebase the same check would be done twice. Could you rebase and gate on that variable instead? It treats a missing output as false, so the else branch here (which no test reaches) is covered too. I ran this on the PR merged with main: subquery.slt and the optimizer unit tests pass, and clippy is clean.
- if pull_up.pull_up_having_expr.is_some() {
- let output_name = subquery_plan.schema().field(0).name();
- let Some(result) = collected_count_expr_map
- .as_ref()
- .and_then(|expr_map| expr_map.get(output_name))
- else {
- return Ok(None);
- };
- if evaluates_to_null(result.clone(), result.column_refs())? {
- return Ok(None);
- }
- }
...
};
+ if pull_up.pull_up_having_expr.is_some() && !output_needs_compensation {
+ return Ok(None);
+ }
if output_needs_compensation && !pull_up.having_like_join_filters.is_empty() {06146b8 to
2f7724a
Compare
|
Thanks for the review, @jayzhan211! I rebased on main and gated on |
…s whose result is NULL on empty input Closes apache#26505.
…reuse output_needs_compensation
2f7724a to
b241047
Compare
|
Rebased on main again. #26904 added the query from #26505 to @jayzhan211, could you take another look when you have time? |
Which issue does this PR close?
HAVINGthat is true for empty input #26505.Rationale for this change
A correlated scalar subquery without
GROUP BYignores itsHAVINGwhen theHAVINGis true for an empty input and the subquery returns an aggregate that is NULL for an empty input (sum,max,min):PullUpCorrelatedExprpulls such aHAVINGout of the subquery, andScalarSubqueryToJoinonly applies it again through the count bug compensationCASE. ThatCASEis not built for expressions that are NULL on empty input, so theHAVINGis dropped.Following #5492 (comment), this makes the query error instead of trying to return the right rows.
What changes are included in this PR?
build_joinkeeps the subquery correlated when aHAVINGwas pulled up and the subquery's output is NULL on empty input. The query then fails withPhysical plan does not support logical expression ScalarSubquery, the same way #26544 does.Only the output column is checked. A
count(*)subquery whoseHAVINGreadssumormaxstill gets theHAVINGfrom the compensation and keeps working.What is the testing strategy for this PR?
New cases in
subquery.slt: the query from the issue,max, andsumwithHAVING count(*) = 0now error. Acount(*)subquery with aHAVINGoncount(*)or onsum, and aHAVINGthat is false on empty input, still return the same rows as before. Allsqllogictestfiles, thedatafusion-optimizerunit tests and thecore_integrationsql::tests pass locally.Are there any user-facing changes?
These queries now return a "not implemented" error instead of wrong results. No API changes.