diff --git a/src/query/sql/src/planner/optimizer/ir/stats/column_stat.rs b/src/query/sql/src/planner/optimizer/ir/stats/column_stat.rs index 8fa854bd56864..5f3fb12db474c 100644 --- a/src/query/sql/src/planner/optimizer/ir/stats/column_stat.rs +++ b/src/query/sql/src/planner/optimizer/ir/stats/column_stat.rs @@ -36,6 +36,9 @@ pub struct ColumnStat { /// Count of null values pub null_count: u64, + pub num_rows: u64, + pub origin_ndv: f64, + /// Histogram of column pub histogram: Option, } diff --git a/src/query/sql/src/planner/optimizer/optimizer.rs b/src/query/sql/src/planner/optimizer/optimizer.rs index 1d91b48fe03fd..e33cc1df53e1f 100644 --- a/src/query/sql/src/planner/optimizer/optimizer.rs +++ b/src/query/sql/src/planner/optimizer/optimizer.rs @@ -263,6 +263,10 @@ pub async fn optimize_query(opt_ctx: Arc, s_expr: SExpr) -> Re ])) // 10. Apply DPhyp algorithm for cost-based join reordering .add(DPhpyOptimizer::new(opt_ctx.clone())) + .add(RecursiveRuleOptimizer::new( + opt_ctx.clone(), + [RuleID::PushDownAntiJoin].as_slice(), + )) // 11. After join reorder, Convert some single join to inner join. .add(SingleToInnerOptimizer::new()) // 12. Deduplicate join conditions. diff --git a/src/query/sql/src/planner/optimizer/optimizers/operator/filter/infer_filter.rs b/src/query/sql/src/planner/optimizer/optimizers/operator/filter/infer_filter.rs index 27b5393399ace..2e3e5daff78f8 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/operator/filter/infer_filter.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/operator/filter/infer_filter.rs @@ -732,6 +732,18 @@ impl<'a> InferFilterOptimizer<'a> { arguments: vec![ self.exprs[equal_indexes[i]].clone(), self.exprs[equal_indexes[j]].clone(), + // ScalarExpr::FunctionCall(FunctionCall { + // span: None, + // func_name: "infer_predicate".to_string(), + // params: vec![], + // arguments: vec![self.exprs[equal_indexes[i]].clone()], + // }), + // ScalarExpr::FunctionCall(FunctionCall { + // span: None, + // func_name: "infer_predicate".to_string(), + // params: vec![], + // arguments: vec![self.exprs[equal_indexes[j]].clone()], + // }), ], })); } diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs index 5791f68e3a279..0f134851dbe3b 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs @@ -59,6 +59,7 @@ use crate::optimizer::optimizers::rule::RulePushDownRankLimitAggregate; use crate::optimizer::optimizers::rule::RulePushDownSortEvalScalar; use crate::optimizer::optimizers::rule::RulePushDownSortFilterScan; use crate::optimizer::optimizers::rule::RulePushDownSortScan; +use crate::optimizer::optimizers::rule::RulePushdownAntiJoin; use crate::optimizer::optimizers::rule::RuleSemiToInnerJoin; use crate::optimizer::optimizers::rule::RuleSplitAggregate; use crate::optimizer::optimizers::rule::RuleTryApplyAggIndex; @@ -130,6 +131,7 @@ impl RuleFactory { RuleID::MergeFilterIntoMutation => { Ok(Box::new(RuleMergeFilterIntoMutation::new(metadata))) } + RuleID::PushDownAntiJoin => Ok(Box::new(RulePushdownAntiJoin::new())), } } } diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/filter_rules/rule_push_down_filter_join.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/filter_rules/rule_push_down_filter_join.rs index f528a1a90c87e..95efbb6fa8d13 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/filter_rules/rule_push_down_filter_join.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/filter_rules/rule_push_down_filter_join.rs @@ -21,8 +21,6 @@ use crate::optimizer::ir::Matcher; use crate::optimizer::ir::RelExpr; use crate::optimizer::ir::SExpr; use crate::optimizer::optimizers::operator::EquivalentConstantsVisitor; -use crate::optimizer::optimizers::operator::InferFilterOptimizer; -use crate::optimizer::optimizers::operator::JoinProperty; use crate::optimizer::optimizers::rule::can_filter_null; use crate::optimizer::optimizers::rule::constant::false_constant; use crate::optimizer::optimizers::rule::constant::is_falsy; @@ -242,9 +240,9 @@ pub fn try_push_down_filter_join(s_expr: &SExpr, metadata: MetadataRef) -> Resul right_push_down = vec![]; } } - let join_prop = JoinProperty::new(&left_prop.output_columns, &right_prop.output_columns); - let mut infer_filter = InferFilterOptimizer::new(Some(join_prop)); - push_down_predicates = infer_filter.optimize(push_down_predicates)?; + // let join_prop = JoinProperty::new(&left_prop.output_columns, &right_prop.output_columns); + // let mut infer_filter = InferFilterOptimizer::new(Some(join_prop)); + // push_down_predicates = infer_filter.optimize(push_down_predicates)?; } let mut all_push_down = vec![]; diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/mod.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/mod.rs index fba55fa30df63..2adad858b586a 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/mod.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/mod.rs @@ -16,6 +16,7 @@ mod push_down_filter_join; mod rule_commute_join; mod rule_commute_join_base_table; mod rule_left_exchange_join; +mod rule_push_down_anti_join; mod rule_semi_to_inner_join; mod util; @@ -23,5 +24,6 @@ pub use push_down_filter_join::*; pub use rule_commute_join::RuleCommuteJoin; pub use rule_commute_join_base_table::RuleCommuteJoinBaseTable; pub use rule_left_exchange_join::RuleLeftExchangeJoin; +pub use rule_push_down_anti_join::RulePushdownAntiJoin; pub use rule_semi_to_inner_join::RuleSemiToInnerJoin; pub use util::get_join_predicates; diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/rule_push_down_anti_join.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/rule_push_down_anti_join.rs new file mode 100644 index 0000000000000..d59c4dd8786e5 --- /dev/null +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/join_rules/rule_push_down_anti_join.rs @@ -0,0 +1,235 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed 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. + +use std::sync::Arc; + +use databend_common_exception::Result; + +use crate::binder::JoinPredicate; +use crate::optimizer::ir::Matcher; +use crate::optimizer::ir::RelExpr; +use crate::optimizer::ir::SExpr; +use crate::optimizer::optimizers::rule::Rule; +use crate::optimizer::optimizers::rule::RuleID; +use crate::optimizer::optimizers::rule::TransformResult; +use crate::plans::Join; +use crate::plans::JoinType; +use crate::plans::RelOp; +use crate::plans::RelOperator; +use crate::ColumnSet; + +/// Push `Left/Right Semi|Anti` join closer to the base table that participates +/// in the predicate so that fewer rows stay in the join tree. +pub struct RulePushdownAntiJoin { + id: RuleID, + matchers: Vec, +} + +impl RulePushdownAntiJoin { + pub fn new() -> Self { + Self { + id: RuleID::PushDownAntiJoin, + matchers: vec![Matcher::MatchOp { + op_type: RelOp::Join, + children: vec![Matcher::Leaf, Matcher::Leaf], + }], + } + } + + fn try_push_down(&self, left: &SExpr, right: &SExpr, join: Join) -> Result> { + let right_rel_expr = RelExpr::with_s_expr(right); + + if let Some(inner_join) = extract_inner_join(left)? { + let inner_join_rel_expr = RelExpr::with_s_expr(&inner_join); + let inner_join_left_prop = inner_join_rel_expr.derive_relational_prop_child(0)?; + let inner_join_right_prop = inner_join_rel_expr.derive_relational_prop_child(1)?; + + let equi_conditions = join + .equi_conditions + .iter() + .map(|condition| { + JoinPredicate::new( + &condition.left, + &inner_join_left_prop, + &inner_join_right_prop, + ) + }) + .collect::>(); + + if equi_conditions.iter().all(left_predicate) { + // let mut new_equi_conditions = Vec::with_capacity(equi_conditions.len()); + + // for (idx, (inferred, predicate)) in equi_conditions.into_iter().enumerate() { + // if !inferred || matches!(predicate, JoinPredicate::Left(_)) { + // new_equi_conditions.push(join.equi_conditions[idx].clone()); + // } + // } + + // join.equi_conditions = new_equi_conditions; + let right_prop = right_rel_expr.derive_relational_prop()?; + let mut union_output_columns = ColumnSet::new(); + union_output_columns.extend(right_prop.output_columns.clone()); + union_output_columns.extend(inner_join_left_prop.output_columns.clone()); + + if join + .non_equi_conditions + .iter() + .all(|x| x.used_columns().is_subset(&union_output_columns)) + { + let new_inner_join = inner_join.replace_children([ + Arc::new(SExpr::create_binary( + RelOperator::Join(join.clone()), + inner_join.child(0)?.clone(), + right.clone(), + )), + Arc::new(inner_join.child(1)?.clone()), + ]); + + return replace_inner_join(left, new_inner_join); + } + } else if equi_conditions.iter().all(right_predicate) { + // let mut new_equi_conditions = Vec::with_capacity(equi_conditions.len()); + + // for (idx, (inferred, predicate)) in equi_conditions.into_iter().enumerate() { + // if !inferred || matches!(predicate, JoinPredicate::Left(_)) { + // new_equi_conditions.push(join.equi_conditions[idx].clone()); + // } + // } + + // join.equi_conditions = new_equi_conditions; + let right_prop = right_rel_expr.derive_relational_prop()?; + let mut union_output_columns = ColumnSet::new(); + union_output_columns.extend(right_prop.output_columns.clone()); + union_output_columns.extend(inner_join_right_prop.output_columns.clone()); + + if join + .non_equi_conditions + .iter() + .all(|x| x.used_columns().is_subset(&union_output_columns)) + { + let new_inner_join = inner_join.replace_children([ + Arc::new(inner_join.child(0)?.clone()), + Arc::new(SExpr::create_binary( + RelOperator::Join(join.clone()), + inner_join.child(1)?.clone(), + right.clone(), + )), + ]); + + return replace_inner_join(left, new_inner_join); + } + } + } + + Ok(None) + } +} + +impl Rule for RulePushdownAntiJoin { + fn id(&self) -> RuleID { + self.id + } + + fn apply(&self, s_expr: &SExpr, state: &mut TransformResult) -> Result<()> { + let join: Join = s_expr.plan().clone().try_into()?; + + if matches!(join.join_type, JoinType::LeftAnti | JoinType::LeftSemi) { + if let Some(mut result) = + self.try_push_down(s_expr.child(0)?, s_expr.child(1)?, join)? + { + result.set_applied_rule(&self.id); + state.add_result(result); + } + } + + Ok(()) + } + + fn matchers(&self) -> &[Matcher] { + &self.matchers + } +} + +impl Default for RulePushdownAntiJoin { + fn default() -> Self { + Self::new() + } +} + +fn replace_inner_join(expr: &SExpr, new_join: SExpr) -> Result> { + match expr.plan() { + RelOperator::Join(join) if join.join_type == JoinType::Inner => Ok(Some(new_join)), + RelOperator::Filter(_) => match replace_inner_join(expr.child(0)?, new_join)? { + None => Ok(None), + Some(new_child) => Ok(Some(expr.replace_children([Arc::new(new_child)]))), + }, + _ => Ok(None), + } +} + +fn extract_inner_join(expr: &SExpr) -> Result> { + match expr.plan() { + RelOperator::Join(join) if join.join_type == JoinType::Inner => Ok(Some(expr.clone())), + RelOperator::Filter(_) => extract_inner_join(expr.child(0)?), + _ => Ok(None), + } +} + +// struct ColumnMappingRewriter<'a> { +// mapping: &'a HashMap, +// } +// +// impl VisitorMut<'_> for ColumnMappingRewriter { +// fn visit_bound_column_ref(&mut self, col: &mut BoundColumnRef) -> Result<()> { +// if let Some(&new_index) = self.mapping.get(&col.column.index) { +// col.column = new_index.clone(); +// } +// Ok(()) +// } +// } +// +// fn replace_by_equivalence( +// expr: &ScalarExpr, +// mapping: &HashMap, +// ) -> Result { +// if mapping.is_empty() { +// return Ok(expr.clone()); +// } +// +// let mut new_expr = expr.clone(); +// let mut rewriter = ColumnMappingRewriter { mapping }; +// rewriter.visit(&mut new_expr)?; +// Ok(new_expr) +// } +// +// fn collect_mapping(join: &Join) -> Result> { +// for equi_condition in &join.equi_conditions { +// match equi_condition.left +// } +// } +// +// fn all_left( +// conditions: &[JoinEquiCondition], +// inner_join: Join, +// left: Arc, +// ) -> Result { +// } + +fn left_predicate(tuple: &JoinPredicate) -> bool { + matches!(&tuple, JoinPredicate::Left(_)) +} + +fn right_predicate(tuple: &JoinPredicate) -> bool { + matches!(&tuple, JoinPredicate::Right(_)) +} diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs index 9eece3b4e9a92..c200dfd9651d4 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs @@ -122,6 +122,7 @@ pub enum RuleID { PushDownSortFilterScan, PushDownLimitFilterScan, SemiToInnerJoin, + PushDownAntiJoin, EliminateEvalScalar, EliminateFilter, EliminateSort, @@ -194,6 +195,7 @@ impl Display for RuleID { RuleID::EliminateUnion => write!(f, "EliminateUnion"), RuleID::MergeFilterIntoMutation => write!(f, "MergeFilterIntoMutation"), + RuleID::PushDownAntiJoin => write!(f, "PushDownAntiJoin"), } } } diff --git a/src/query/sql/src/planner/plans/constant_table_scan.rs b/src/query/sql/src/planner/plans/constant_table_scan.rs index 3dd153b4fc926..bc07b4c837993 100644 --- a/src/query/sql/src/planner/plans/constant_table_scan.rs +++ b/src/query/sql/src/planner/plans/constant_table_scan.rs @@ -225,6 +225,8 @@ impl Operator for ConstantTableScan { max, ndv: ndv as f64, null_count, + num_rows: self.num_rows as u64, + origin_ndv: ndv as f64, histogram, }; column_stats.insert(*index, column_stat); diff --git a/src/query/sql/src/planner/plans/join.rs b/src/query/sql/src/planner/plans/join.rs index 7cc07b8b3909d..6dd64b0066e8a 100644 --- a/src/query/sql/src/planner/plans/join.rs +++ b/src/query/sql/src/planner/plans/join.rs @@ -493,10 +493,48 @@ impl Join { + f64::max(right_cardinality, inner_join_cardinality) - inner_join_cardinality } - JoinType::LeftSemi => f64::min(left_cardinality, inner_join_cardinality), - JoinType::RightSemi => f64::min(right_cardinality, inner_join_cardinality), - JoinType::LeftSingle | JoinType::RightMark | JoinType::LeftAnti => left_cardinality, - JoinType::RightSingle | JoinType::LeftMark | JoinType::RightAnti => right_cardinality, + JoinType::LeftSemi => { + let left_exprs = self + .equi_conditions + .iter() + .map(|x| &x.left) + .collect::>(); + + self.semi_cardinality(left_cardinality, &left_statistics, &left_exprs) + } + JoinType::RightSemi => { + let right_exprs = self + .equi_conditions + .iter() + .map(|x| &x.right) + .collect::>(); + + self.semi_cardinality(right_cardinality, &right_statistics, &right_exprs) + } + JoinType::LeftAnti => { + let left_exprs = self + .equi_conditions + .iter() + .map(|x| &x.left) + .collect::>(); + self.anti_cardinality(left_cardinality, &left_statistics, &left_exprs) + // left_cardinality + // left_cardinality + // * self.compute_selectivity_for_build_side(right_cardinality, &right_statistics) + } + JoinType::RightAnti => { + let right_exprs = self + .equi_conditions + .iter() + .map(|x| &x.right) + .collect::>(); + + self.anti_cardinality(right_cardinality, &right_statistics, &right_exprs) + // right_cardinality + // * self.compute_selectivity_for_build_side(right_cardinality, &right_statistics) + } + JoinType::LeftSingle | JoinType::RightMark => left_cardinality, + JoinType::RightSingle | JoinType::LeftMark => right_cardinality, }; // Derive column statistics let column_stats = if cardinality == 0.0 { @@ -544,6 +582,84 @@ impl Join { .iter() .any(|expr| expr.has_subquery()) } + + fn anti_cardinality( + &self, + cardinality: f64, + statistics: &Statistics, + exprs: &[&ScalarExpr], + ) -> f64 { + let mut anti_cardinality = cardinality; + for expr in exprs { + let mut used_columns = expr.used_columns(); + + let (Some(column), None) = (used_columns.pop_first(), used_columns.pop_first()) else { + continue; + }; + + if let Some(column_stat) = statistics.column_stats.get(&column) { + let semi_cardinality = cardinality * column_stat.ndv / column_stat.origin_ndv; + let column_cardinality = (cardinality - semi_cardinality).max(cardinality * 0.3); + anti_cardinality = 1_f64.max(column_cardinality.min(anti_cardinality)); + } + } + + anti_cardinality + } + + fn semi_cardinality( + &self, + cardinality: f64, + statistics: &Statistics, + exprs: &[&ScalarExpr], + ) -> f64 { + let mut semi_cardinality = cardinality; + for expr in exprs { + let mut used_columns = expr.used_columns(); + + let (Some(column), None) = (used_columns.pop_first(), used_columns.pop_first()) else { + continue; + }; + + if let Some(column_stat) = statistics.column_stats.get(&column) { + let column_cardinality = cardinality * column_stat.ndv / column_stat.origin_ndv; + semi_cardinality = 1_f64.max(column_cardinality.min(semi_cardinality)); + } + } + + semi_cardinality + } + + // fn compute_selectivity_for_build_side( + // &self, + // build_cardinality: f64, + // build_statistics: &Statistics, + // ) -> f64 { + // let mut selectivity = 1.0_f64; + // for equi_condition in &self.equi_conditions { + // let expr = &equi_condition.right; + // + // if expr.used_columns().len() != 1 { + // continue; + // } + // + // let Some(right_col_stat) = build_statistics + // .column_stats + // .get(expr.used_columns().iter().next().unwrap()) + // else { + // continue; + // }; + // + // if right_col_stat.num_rows == 0 { + // continue; + // } + // + // let num_rows = right_col_stat.num_rows as f64; + // selectivity = selectivity.min(0.5_f64.max(1.0_f64.min(build_cardinality / num_rows))); + // } + // + // selectivity + // } } impl Operator for Join { diff --git a/src/query/sql/src/planner/plans/scan.rs b/src/query/sql/src/planner/plans/scan.rs index d47b83b367be7..35bc636874db0 100644 --- a/src/query/sql/src/planner/plans/scan.rs +++ b/src/query/sql/src/planner/plans/scan.rs @@ -259,6 +259,7 @@ impl Operator for Scan { .unwrap_or(0); let mut column_stats: ColumnStatSet = Default::default(); + for (k, v) in &self.statistics.column_stats { // No need to cal histogram for unused columns if !used_columns.contains(k) { @@ -311,11 +312,14 @@ impl Operator for Scan { None } }; + let column_stat = ColumnStat { min, max, ndv: ndv as f64, null_count: col_stat.null_count, + origin_ndv: ndv as f64, + num_rows, histogram, }; column_stats.insert(*k as IndexType, column_stat); diff --git a/tests/sqllogictests/suites/mode/cluster/filter_nulls.test b/tests/sqllogictests/suites/mode/cluster/filter_nulls.test index 29a426f6c1e31..c1cca76af3358 100644 --- a/tests/sqllogictests/suites/mode/cluster/filter_nulls.test +++ b/tests/sqllogictests/suites/mode/cluster/filter_nulls.test @@ -177,7 +177,7 @@ Exchange ├── probe keys: [table1.value (#0)] ├── keys is null equal: [false] ├── filters: [] - ├── estimated rows: 250.00 + ├── estimated rows: 225.00 ├── Exchange(Build) │ ├── output columns: [table2.value (#1)] │ ├── exchange type: Broadcast @@ -227,7 +227,7 @@ Exchange ├── filters: [] ├── build join filters(distributed): │ └── filter id:0, build key:table2.value (#1), probe targets:[table1.value (#0)@scan0], filter type:bloom,inlist,min_max - ├── estimated rows: 250.00 + ├── estimated rows: 232.68 ├── Exchange(Build) │ ├── output columns: [table2.value (#1)] │ ├── exchange type: Hash(table2.value (#1)) diff --git a/tests/sqllogictests/suites/mode/standalone/explain/delete.test b/tests/sqllogictests/suites/mode/standalone/explain/delete.test index a327075f37020..c2acb6f064ea2 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain/delete.test +++ b/tests/sqllogictests/suites/mode/standalone/explain/delete.test @@ -97,7 +97,7 @@ CommitSink ├── probe keys: [t1.a (#0)] ├── keys is null equal: [false] ├── filters: [] - ├── estimated rows: 2.00 + ├── estimated rows: 1.50 ├── TableScan(Build) │ ├── table: default.default.t2 │ ├── scan id: 1 diff --git a/tests/sqllogictests/suites/mode/standalone/explain/join_reorder/chain.test b/tests/sqllogictests/suites/mode/standalone/explain/join_reorder/chain.test index a15373c28d91b..91937bdf83f91 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain/join_reorder/chain.test +++ b/tests/sqllogictests/suites/mode/standalone/explain/join_reorder/chain.test @@ -635,7 +635,7 @@ HashJoin ├── probe keys: [t1.a (#1)] ├── keys is null equal: [false] ├── filters: [] -├── estimated rows: 10.00 +├── estimated rows: 9.00 ├── TableScan(Build) │ ├── table: default.join_reorder.t │ ├── scan id: 0 diff --git a/tests/sqllogictests/suites/mode/standalone/explain/subquery.test b/tests/sqllogictests/suites/mode/standalone/explain/subquery.test index a9ef7e73722a3..4ce9ff4a8efff 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain/subquery.test +++ b/tests/sqllogictests/suites/mode/standalone/explain/subquery.test @@ -339,7 +339,7 @@ HashJoin ├── probe keys: [t.number (#0)] ├── keys is null equal: [false] ├── filters: [] -├── estimated rows: 0.25 +├── estimated rows: 0.50 ├── Filter(Build) │ ├── output columns: [numbers.number (#1)] │ ├── filters: [numbers.number (#1) < 10, numbers.number (#1) = 0] diff --git a/tests/sqllogictests/suites/mode/standalone/explain/update.test b/tests/sqllogictests/suites/mode/standalone/explain/update.test index 5bd0cb54e4bdb..43eae21639766 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain/update.test +++ b/tests/sqllogictests/suites/mode/standalone/explain/update.test @@ -103,7 +103,7 @@ CommitSink ├── probe keys: [t1.a (#0)] ├── keys is null equal: [false] ├── filters: [] - ├── estimated rows: 2.00 + ├── estimated rows: 1.50 ├── TableScan(Build) │ ├── table: default.default.t2 │ ├── scan id: 1 @@ -135,7 +135,7 @@ query T explain analyze partial update t1 set a = a + 1 where a in (select a from t2) and b > 2; ---- HashJoin: LEFT SEMI -├── estimated rows: 2.00 +├── estimated rows: 1.50 ├── output rows: 2 ├── TableScan │ ├── table: default.default.t2 diff --git a/tests/sqllogictests/suites/mode/standalone/explain_native/join_reorder/chain.test b/tests/sqllogictests/suites/mode/standalone/explain_native/join_reorder/chain.test index efa878f4e2d65..003ad890a1d19 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain_native/join_reorder/chain.test +++ b/tests/sqllogictests/suites/mode/standalone/explain_native/join_reorder/chain.test @@ -573,7 +573,7 @@ HashJoin ├── probe keys: [t1.a (#1)] ├── keys is null equal: [false] ├── filters: [] -├── estimated rows: 10.00 +├── estimated rows: 9.00 ├── TableScan(Build) │ ├── table: default.join_reorder.t │ ├── scan id: 0 diff --git a/tests/sqllogictests/suites/mode/standalone/explain_native/push_down_filter/push_down_filter_join/push_down_filter_join_semi_anti.test b/tests/sqllogictests/suites/mode/standalone/explain_native/push_down_filter/push_down_filter_join/push_down_filter_join_semi_anti.test index af7e524cbbdb4..76602a501fdd8 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain_native/push_down_filter/push_down_filter_join/push_down_filter_join_semi_anti.test +++ b/tests/sqllogictests/suites/mode/standalone/explain_native/push_down_filter/push_down_filter_join/push_down_filter_join_semi_anti.test @@ -28,7 +28,7 @@ HashJoin ├── probe keys: [t1.a (#0)] ├── keys is null equal: [false] ├── filters: [] -├── estimated rows: 0.00 +├── estimated rows: 1.00 ├── TableScan(Build) │ ├── table: default.default.t2 │ ├── scan id: 1 @@ -63,7 +63,7 @@ HashJoin ├── probe keys: [t1.a (#0)] ├── keys is null equal: [false] ├── filters: [] -├── estimated rows: 0.00 +├── estimated rows: 1.00 ├── TableScan(Build) │ ├── table: default.default.t2 │ ├── scan id: 1 diff --git a/tests/sqllogictests/suites/mode/standalone/explain_native/subquery.test b/tests/sqllogictests/suites/mode/standalone/explain_native/subquery.test index 19eea51fc861e..4cc5e0b14a2d0 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain_native/subquery.test +++ b/tests/sqllogictests/suites/mode/standalone/explain_native/subquery.test @@ -314,7 +314,7 @@ HashJoin ├── probe keys: [t.number (#0)] ├── keys is null equal: [false] ├── filters: [] -├── estimated rows: 0.25 +├── estimated rows: 0.50 ├── Filter(Build) │ ├── output columns: [numbers.number (#1)] │ ├── filters: [numbers.number (#1) < 10, numbers.number (#1) = 0]