Skip to content

Commit c2bc5dd

Browse files
committed
fix
1 parent 608f08a commit c2bc5dd

5 files changed

Lines changed: 160 additions & 149 deletions

File tree

src/query/sql/src/planner/plans/union_all.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ impl UnionAll {
133133
{
134134
return Ok(Some((output, right)));
135135
}
136+
// TODO: Distinguish all-NULL column statistics from unknown statistics so
137+
// a non-empty all-NULL branch can contribute its NULL count without
138+
// discarding the other branch's value statistics.
136139
_ => return Ok(None),
137140
};
138141
let mut ndv = Self::merge_ndv(&left, &right)?;
@@ -179,6 +182,14 @@ impl UnionAll {
179182

180183
fn merge_ndv(left: &ColumnStat, right: &ColumnStat) -> Result<NdvEstimate> {
181184
let ndv_upper = left.ndv.upper + right.ndv.upper;
185+
let ranges_disjoint = left.max.compare(&right.min)? == Ordering::Less
186+
|| right.max.compare(&left.min)? == Ordering::Less;
187+
if ranges_disjoint
188+
&& let (Some(left), Some(right)) = (left.ndv.expected, right.ndv.expected)
189+
{
190+
return Ok(NdvEstimate::new(left + right, ndv_upper));
191+
}
192+
182193
if left.min.is_numeric()
183194
&& let (Some(left), Some(left_ndv)) = (&left.histogram, left.ndv.expected)
184195
&& let (Some(right), Some(right_ndv)) = (&right.histogram, right.ndv.expected)

src/query/sql/test-support/data/results/regressions/19574_correlated_exists_union_optimized.txt

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,64 @@ EvalScalar
22
├── scalars: [f1 (#0) AS (#0), marker (#7) AS (#8)]
33
└── Filter
44
├── filters: [is_true(marker (#7))]
5-
└── Join(RightMark)
6-
├── build keys: [f1 (#6)]
7-
├── probe keys: [f1 (#0)]
5+
└── Join(LeftMark)
6+
├── build keys: [f1 (#0)]
7+
├── probe keys: [f1 (#6)]
88
├── other filters: []
9-
├── Aggregate(Final)
10-
── group items: [1 (#3) AS (#3), f1 (#6) AS (#6)]
11-
│ ├── aggregate functions: []
12-
│ └── Aggregate(Partial)
13-
│ ├── group items: [1 (#3) AS (#3), f1 (#6) AS (#6)]
14-
├── aggregate functions: []
15-
── UnionAll
16-
── output: [1 (#3), f1 (#6)]
17-
├── left: [1 (#1), f1 (#4)]
18-
├── right: [2 (#2), f1 (#5)]
19-
── cte_scan_names: []
20-
├── logical_recursive_cte_id: None
21-
├── Join(Cross)
22-
├── build keys: []
23-
├── probe keys: []
24-
├── other filters: []
25-
├── EvalScalar
26-
│ ├── scalars: [1 AS (#1)]
27-
── DummyTableScan(DummyTableScan { source_table_indexes: [] })
28-
── Exchange(Merge)
29-
── Aggregate(Final)
30-
├── group items: [f1 (#4) AS (#4)]
31-
── aggregate functions: []
32-
└── Aggregate(Partial)
33-
── group items: [f1 (#4) AS (#4)]
34-
├── aggregate functions: []
35-
── Exchange(Hash)
36-
── Exchange(Hash): keys: [f1 (#4)]
37-
└── ConstantTableScan
38-
├── columns: [f1 (#4)]
39-
└── num_rows: [1]
40-
── EvalScalar
41-
├── scalars: [2 AS (#2), f1 (#5) AS (#5)]
42-
── Join(Cross)
43-
── build keys: []
44-
── probe keys: []
45-
├── other filters: []
46-
── DummyTableScan(DummyTableScan { source_table_indexes: [] })
47-
── Exchange(Merge)
48-
── Aggregate(Final)
49-
├── group items: [f1 (#5) AS (#5)]
50-
├── aggregate functions: []
51-
└── Aggregate(Partial)
52-
── group items: [f1 (#5) AS (#5)]
53-
├── aggregate functions: []
54-
── Exchange(Hash)
55-
── Exchange(Hash): keys: [f1 (#5)]
56-
└── Filter
57-
├── filters: [eq(f1 (#5), 1)]
58-
└── ConstantTableScan
59-
├── columns: [f1 (#5)]
60-
└── num_rows: [1]
61-
── Exchange(Merge)
62-
└── ConstantTableScan
63-
├── columns: [f1 (#0)]
64-
└── num_rows: [1]
9+
├── Exchange(Merge)
10+
── ConstantTableScan
11+
├── columns: [f1 (#0)]
12+
└── num_rows: [1]
13+
── Aggregate(Final)
14+
├── group items: [1 (#3) AS (#3), f1 (#6) AS (#6)]
15+
── aggregate functions: []
16+
── Aggregate(Partial)
17+
├── group items: [1 (#3) AS (#3), f1 (#6) AS (#6)]
18+
├── aggregate functions: []
19+
── UnionAll
20+
├── output: [1 (#3), f1 (#6)]
21+
├── left: [1 (#1), f1 (#4)]
22+
├── right: [2 (#2), f1 (#5)]
23+
├── cte_scan_names: []
24+
├── logical_recursive_cte_id: None
25+
├── Join(Cross)
26+
│ ├── build keys: []
27+
── probe keys: []
28+
── other filters: []
29+
── EvalScalar
30+
├── scalars: [1 AS (#1)]
31+
── DummyTableScan(DummyTableScan { source_table_indexes: [] })
32+
│ └── Exchange(Merge)
33+
── Aggregate(Final)
34+
│ ├── group items: [f1 (#4) AS (#4)]
35+
── aggregate functions: []
36+
── Aggregate(Partial)
37+
├── group items: [f1 (#4) AS (#4)]
38+
│ ├── aggregate functions: []
39+
│ └── Exchange(Hash)
40+
── Exchange(Hash): keys: [f1 (#4)]
41+
└── ConstantTableScan
42+
── columns: [f1 (#4)]
43+
── num_rows: [1]
44+
── EvalScalar
45+
├── scalars: [2 AS (#2), f1 (#5) AS (#5)]
46+
── Join(Cross)
47+
── build keys: []
48+
── probe keys: []
49+
├── other filters: []
50+
├── DummyTableScan(DummyTableScan { source_table_indexes: [] })
51+
└── Exchange(Merge)
52+
── Aggregate(Final)
53+
├── group items: [f1 (#5) AS (#5)]
54+
── aggregate functions: []
55+
── Aggregate(Partial)
56+
├── group items: [f1 (#5) AS (#5)]
57+
├── aggregate functions: []
58+
└── Exchange(Hash)
59+
├── Exchange(Hash): keys: [f1 (#5)]
60+
└── Filter
61+
── filters: [eq(f1 (#5), 1)]
62+
└── ConstantTableScan
63+
├── columns: [f1 (#5)]
64+
└── num_rows: [1]
6565

src/query/sql/test-support/data/results/regressions/19574_correlated_exists_union_physical.txt

Lines changed: 87 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,94 +4,94 @@ Filter
44
├── estimated rows: 0.20
55
└── HashJoin
66
├── output columns: [f1 (#0), marker (#7)]
7-
├── join type: RIGHT MARK
8-
├── build keys: [f1 (#6)]
9-
├── probe keys: [f1 (#0)]
7+
├── join type: LEFT MARK
8+
├── build keys: [f1 (#0)]
9+
├── probe keys: [f1 (#6)]
1010
├── keys is null equal: [false]
1111
├── filters: []
1212
├── estimated rows: 1.00
13-
├── AggregateFinal(Build)
14-
│ ├── output columns: [1 (#3), f1 (#6)]
15-
│ ├── group by: [1, f1]
16-
── aggregate functions: []
17-
│ ├── estimated rows: 1.00
18-
│ └── AggregatePartial
19-
│ ├── group by: [1, f1]
20-
├── aggregate functions: []
21-
├── estimated rows: 1.00
22-
── UnionAll
23-
├── output columns: [1 (#3), f1 (#6)]
24-
── estimated rows: 2.00
25-
├── HashJoin
26-
├── output columns: [f1 (#4), 1 (#1)]
27-
├── join type: CROSS
28-
│ ├── build keys: []
29-
├── probe keys: []
30-
├── keys is null equal: []
31-
├── filters: []
32-
│ ├── estimated rows: 1.00
33-
│ ├── EvalScalar(Build)
34-
│ ├── output columns: [1 (#1)]
35-
│ ├── expressions: [1]
36-
│ ├── estimated rows: 1.00
37-
── DummyTableScan
38-
── Exchange(Probe)
39-
├── output columns: [f1 (#4)]
40-
├── exchange type: Merge
41-
── AggregateFinal
42-
├── output columns: [f1 (#4)]
43-
── group by: [f1]
44-
── aggregate functions: []
45-
├── estimated rows: 1.00
46-
── Exchange
47-
── output columns: [f1 (#4)]
48-
├── exchange type: Hash(0)
49-
── AggregatePartial
50-
├── group by: [f1]
51-
├── aggregate functions: []
52-
── estimated rows: 1.00
53-
└── ConstantTableScan
54-
├── output columns: [f1 (#4)]
55-
└── column 0: [1]
56-
── EvalScalar
57-
│ ├── output columns: [f1 (#5), 2 (#2)]
58-
│ ├── expressions: [2]
59-
├── estimated rows: 1.00
60-
── HashJoin
61-
── output columns: [f1 (#5)]
62-
── join type: CROSS
63-
├── build keys: []
64-
├── probe keys: []
65-
├── keys is null equal: []
66-
── filters: []
67-
├── estimated rows: 1.00
68-
├── DummyTableScan(Build)
69-
── Exchange(Probe)
70-
├── output columns: [f1 (#5)]
71-
├── exchange type: Merge
72-
── AggregateFinal
73-
├── output columns: [f1 (#5)]
74-
├── group by: [f1]
75-
── aggregate functions: []
76-
├── estimated rows: 1.00
77-
── Exchange
78-
── output columns: [f1 (#5)]
79-
├── exchange type: Hash(0)
80-
── AggregatePartial
81-
├── group by: [f1]
82-
├── aggregate functions: []
83-
── estimated rows: 1.00
84-
└── Filter
85-
├── output columns: [f1 (#5)]
86-
── filters: [outer.f1 (#5) = 1]
87-
├── estimated rows: 1.00
88-
── ConstantTableScan
89-
├── output columns: [f1 (#5)]
90-
└── column 0: [1]
91-
── Exchange(Probe)
92-
├── output columns: [f1 (#0)]
93-
├── exchange type: Merge
94-
└── ConstantTableScan
95-
├── output columns: [f1 (#0)]
96-
└── column 0: [1]
13+
├── Exchange(Build)
14+
│ ├── output columns: [f1 (#0)]
15+
│ ├── exchange type: Merge
16+
── ConstantTableScan
17+
├── output columns: [f1 (#0)]
18+
└── column 0: [1]
19+
── AggregateFinal(Probe)
20+
├── output columns: [1 (#3), f1 (#6)]
21+
├── group by: [1, f1]
22+
── aggregate functions: []
23+
├── estimated rows: 2.00
24+
── AggregatePartial
25+
├── group by: [1, f1]
26+
├── aggregate functions: []
27+
├── estimated rows: 2.00
28+
── UnionAll
29+
├── output columns: [1 (#3), f1 (#6)]
30+
├── estimated rows: 2.00
31+
├── HashJoin
32+
│ ├── output columns: [f1 (#4), 1 (#1)]
33+
│ ├── join type: CROSS
34+
│ ├── build keys: []
35+
│ ├── probe keys: []
36+
│ ├── keys is null equal: []
37+
── filters: []
38+
── estimated rows: 1.00
39+
│ ├── EvalScalar(Build)
40+
├── output columns: [1 (#1)]
41+
── expressions: [1]
42+
├── estimated rows: 1.00
43+
── DummyTableScan
44+
── Exchange(Probe)
45+
│ ├── output columns: [f1 (#4)]
46+
── exchange type: Merge
47+
── AggregateFinal
48+
│ ├── output columns: [f1 (#4)]
49+
── group by: [f1]
50+
│ ├── aggregate functions: []
51+
│ ├── estimated rows: 1.00
52+
── Exchange
53+
├── output columns: [f1 (#4)]
54+
│ ├── exchange type: Hash(0)
55+
│ └── AggregatePartial
56+
── group by: [f1]
57+
├── aggregate functions: []
58+
├── estimated rows: 1.00
59+
└── ConstantTableScan
60+
── output columns: [f1 (#4)]
61+
── column 0: [1]
62+
── EvalScalar
63+
├── output columns: [f1 (#5), 2 (#2)]
64+
├── expressions: [2]
65+
├── estimated rows: 1.00
66+
── HashJoin
67+
├── output columns: [f1 (#5)]
68+
├── join type: CROSS
69+
── build keys: []
70+
├── probe keys: []
71+
├── keys is null equal: []
72+
── filters: []
73+
├── estimated rows: 1.00
74+
├── DummyTableScan(Build)
75+
── Exchange(Probe)
76+
├── output columns: [f1 (#5)]
77+
── exchange type: Merge
78+
── AggregateFinal
79+
├── output columns: [f1 (#5)]
80+
── group by: [f1]
81+
├── aggregate functions: []
82+
├── estimated rows: 1.00
83+
── Exchange
84+
├── output columns: [f1 (#5)]
85+
├── exchange type: Hash(0)
86+
── AggregatePartial
87+
├── group by: [f1]
88+
── aggregate functions: []
89+
├── estimated rows: 1.00
90+
└── Filter
91+
── output columns: [f1 (#5)]
92+
├── filters: [outer.f1 (#5) = 1]
93+
├── estimated rows: 1.00
94+
└── ConstantTableScan
95+
├── output columns: [f1 (#5)]
96+
└── column 0: [1]
9797

src/query/sql/tests/it/optimizer/union_all.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ fn cases() -> Result<Vec<UnionCase>> {
261261
Ok(vec![
262262
UnionCase {
263263
name: "lossless_coercion",
264-
description: "SQL binding inserts a nullable INT to BIGINT coercion and UnionAll derives statistics after the cast.",
264+
description: "SQL binding inserts a nullable INT to BIGINT coercion; after the cast drops the histogram, disjoint ranges still make the union NDV additive.",
265265
sql: "SELECT k FROM l UNION ALL SELECT k FROM r",
266266
tables: vec![
267267
table(

src/query/sql/tests/it/optimizer/union_all.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=== lossless_coercion ===
2-
description: SQL binding inserts a nullable INT to BIGINT coercion and UnionAll derives statistics after the cast.
2+
description: SQL binding inserts a nullable INT to BIGINT coercion; after the cast drops the histogram, disjoint ranges still make the union NDV additive.
33
sql: SELECT k FROM l UNION ALL SELECT k FROM r
44
path: []
55
node:
@@ -22,7 +22,7 @@ UnionAll
2222

2323
cardinality: 30.000
2424
precise_cardinality: Some(30)
25-
column_stat: k (#2) min=1, max=8, ndv=~5.0[..8.0], null_count=3, histogram=none
25+
column_stat: k (#2) min=1, max=8, ndv=8.0, null_count=3, histogram=none
2626

2727
=== finite_range_reduces_union_ndv_bound ===
2828
description: UnionAll reduces its conservative NDV upper bound to the merged finite range before an outer cast consumes the statistics.

0 commit comments

Comments
 (0)