Skip to content

Commit b2db45f

Browse files
committed
Remove distinct from min aggregations
1 parent 5bf6aef commit b2db45f

4 files changed

Lines changed: 71 additions & 11 deletions

File tree

‎datafusion/functions-aggregate/src/min_max.rs‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ use crate::min_max::min_max_bytes::MinMaxBytesAccumulator;
4646
use crate::min_max::min_max_struct::MinMaxStructAccumulator;
4747
use datafusion_common::ScalarValue;
4848
use datafusion_expr::{
49-
Accumulator, AggregateUDFImpl, Documentation, SetMonotonicity, Signature, Volatility,
50-
function::AccumulatorArgs,
49+
Accumulator, AggregateUDFImpl, Documentation, Expr, SetMonotonicity, Signature,
50+
Volatility,
51+
function::{AccumulatorArgs, AggregateFunctionSimplification},
5152
};
5253
use datafusion_expr::{GroupsAccumulator, StatisticsArgs};
5354
use datafusion_macros::user_doc;
@@ -685,6 +686,14 @@ impl AggregateUDFImpl for Min {
685686
datafusion_expr::ReversedUDAF::Identical
686687
}
687688

689+
fn simplify(&self) -> Option<AggregateFunctionSimplification> {
690+
// `min(DISTINCT x)` is identical to `min(x)`, therefore drop DISTINCT.
691+
Some(Box::new(|mut aggregate_function, _info| {
692+
aggregate_function.params.distinct = false;
693+
Ok(Expr::AggregateFunction(aggregate_function))
694+
}))
695+
}
696+
688697
fn documentation(&self) -> Option<&Documentation> {
689698
self.doc()
690699
}

‎datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2433,6 +2433,7 @@ mod tests {
24332433
interval_arithmetic::Interval,
24342434
*,
24352435
};
2436+
use datafusion_functions_aggregate::min_max::min_udaf;
24362437
use datafusion_functions_window_common::field::WindowUDFFieldArgs;
24372438
use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
24382439
use datafusion_physical_expr::PhysicalExpr;
@@ -5245,6 +5246,23 @@ mod tests {
52455246
assert_eq!(simplify(aggregate_function_expr), expected);
52465247
}
52475248

5249+
#[test]
5250+
fn test_simplify_min_drops_distinct() {
5251+
let min_agg = |distinct: bool| {
5252+
Expr::AggregateFunction(expr::AggregateFunction::new_udf(
5253+
min_udaf(),
5254+
vec![col("c3")],
5255+
distinct,
5256+
None,
5257+
vec![],
5258+
None,
5259+
))
5260+
};
5261+
5262+
let simplified = simplify(min_agg(true));
5263+
assert_eq!(simplified, min_agg(false));
5264+
}
5265+
52485266
/// A Mock UDAF which defines `simplify` to be used in tests
52495267
/// related to UDAF simplification
52505268
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

‎datafusion/optimizer/src/single_distinct_to_groupby.rs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ impl SingleDistinctToGroupBy {
6161
}
6262
}
6363

64+
fn unalias_aggregate(expr: &Expr) -> &Expr {
65+
match expr {
66+
Expr::Alias(alias) if matches!(*alias.expr, Expr::AggregateFunction(_)) => {
67+
&alias.expr
68+
}
69+
_ => expr,
70+
}
71+
}
72+
6473
/// Check whether all aggregate exprs are distinct on a single field.
6574
fn is_single_distinct_agg(aggr_expr: &[Expr]) -> Result<bool> {
6675
let mut fields_set = HashSet::new();
@@ -76,7 +85,7 @@ fn is_single_distinct_agg(aggr_expr: &[Expr]) -> Result<bool> {
7685
order_by,
7786
null_treatment: _,
7887
},
79-
}) = expr
88+
}) = unalias_aggregate(expr)
8089
{
8190
if filter.is_some() || !order_by.is_empty() {
8291
return Ok(false);
@@ -179,6 +188,7 @@ impl OptimizerRule for SingleDistinctToGroupBy {
179188
let mut inner_aggr_exprs = vec![];
180189
let outer_aggr_exprs = aggr_expr
181190
.into_iter()
191+
.map(|aggr_expr| aggr_expr.unalias())
182192
.map(|aggr_expr| match aggr_expr {
183193
Expr::AggregateFunction(AggregateFunction {
184194
func,

‎datafusion/sqllogictest/test_files/group_by.slt‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4249,6 +4249,29 @@ physical_plan
42494249
07)------------AggregateExec: mode=Partial, gby=[y@1 as y, CAST(x@0 AS Float64) as alias1], aggr=[]
42504250
08)--------------DataSourceExec: partitions=1, partition_sizes=[1]
42514251

4252+
4253+
statement ok
4254+
CREATE TABLE min_distinct(g int, x int) AS VALUES
4255+
(1, 3), (1, 3), (1, 1), (1, NULL),
4256+
(2, NULL), (2, NULL),
4257+
(3, 7), (3, 5), (3, 5);
4258+
4259+
query TT
4260+
EXPLAIN SELECT g, min(DISTINCT x) FROM min_distinct GROUP BY g;
4261+
----
4262+
logical_plan
4263+
01)Aggregate: groupBy=[[min_distinct.g]], aggr=[[min(min_distinct.x) AS min(DISTINCT min_distinct.x)]]
4264+
02)--TableScan: min_distinct projection=[g, x]
4265+
physical_plan
4266+
01)AggregateExec: mode=FinalPartitioned, gby=[g@0 as g], aggr=[min(min_distinct.x) as min(DISTINCT min_distinct.x)]
4267+
02)--RepartitionExec: partitioning=Hash([g@0], 8), input_partitions=8
4268+
03)----AggregateExec: mode=Partial, gby=[g@0 as g], aggr=[min(min_distinct.x) as min(DISTINCT min_distinct.x)]
4269+
04)------RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
4270+
05)--------DataSourceExec: partitions=1, partition_sizes=[5]
4271+
4272+
statement ok
4273+
DROP TABLE min_distinct;
4274+
42524275
# create an unbounded table that contains ordered timestamp.
42534276
statement ok
42544277
CREATE UNBOUNDED EXTERNAL TABLE unbounded_csv_with_timestamps (
@@ -4432,20 +4455,20 @@ EXPLAIN SELECT c1, count(distinct c2), min(distinct c2), sum(c3), max(c4) FROM a
44324455
----
44334456
logical_plan
44344457
01)Sort: aggregate_test_100.c1 ASC NULLS LAST
4435-
02)--Projection: aggregate_test_100.c1, count(alias1) AS count(DISTINCT aggregate_test_100.c2), min(alias1) AS min(DISTINCT aggregate_test_100.c2), sum(alias2) AS sum(aggregate_test_100.c3), max(alias3) AS max(aggregate_test_100.c4)
4436-
03)----Aggregate: groupBy=[[aggregate_test_100.c1]], aggr=[[count(alias1), min(alias1), sum(alias2), max(alias3)]]
4437-
04)------Aggregate: groupBy=[[aggregate_test_100.c1, aggregate_test_100.c2 AS alias1]], aggr=[[sum(CAST(aggregate_test_100.c3 AS Int64)) AS alias2, max(aggregate_test_100.c4) AS alias3]]
4458+
02)--Projection: aggregate_test_100.c1, count(alias1) AS count(DISTINCT aggregate_test_100.c2), min(alias2) AS min(DISTINCT aggregate_test_100.c2), sum(alias3) AS sum(aggregate_test_100.c3), max(alias4) AS max(aggregate_test_100.c4)
4459+
03)----Aggregate: groupBy=[[aggregate_test_100.c1]], aggr=[[count(alias1), min(alias2), sum(alias3), max(alias4)]]
4460+
04)------Aggregate: groupBy=[[aggregate_test_100.c1, aggregate_test_100.c2 AS alias1]], aggr=[[min(aggregate_test_100.c2) AS alias2, sum(CAST(aggregate_test_100.c3 AS Int64)) AS alias3, max(aggregate_test_100.c4) AS alias4]]
44384461
05)--------TableScan: aggregate_test_100 projection=[c1, c2, c3, c4]
44394462
physical_plan
44404463
01)SortPreservingMergeExec: [c1@0 ASC NULLS LAST]
4441-
02)--ProjectionExec: expr=[c1@0 as c1, count(alias1)@1 as count(DISTINCT aggregate_test_100.c2), min(alias1)@2 as min(DISTINCT aggregate_test_100.c2), sum(alias2)@3 as sum(aggregate_test_100.c3), max(alias3)@4 as max(aggregate_test_100.c4)]
4464+
02)--ProjectionExec: expr=[c1@0 as c1, count(alias1)@1 as count(DISTINCT aggregate_test_100.c2), min(alias2)@2 as min(DISTINCT aggregate_test_100.c2), sum(alias3)@3 as sum(aggregate_test_100.c3), max(alias4)@4 as max(aggregate_test_100.c4)]
44424465
03)----SortExec: expr=[c1@0 ASC NULLS LAST], preserve_partitioning=[true]
4443-
04)------AggregateExec: mode=FinalPartitioned, gby=[c1@0 as c1], aggr=[count(alias1), min(alias1), sum(alias2), max(alias3)]
4466+
04)------AggregateExec: mode=FinalPartitioned, gby=[c1@0 as c1], aggr=[count(alias1), min(alias2), sum(alias3), max(alias4)]
44444467
05)--------RepartitionExec: partitioning=Hash([c1@0], 8), input_partitions=8
4445-
06)----------AggregateExec: mode=Partial, gby=[c1@0 as c1], aggr=[count(alias1), min(alias1), sum(alias2), max(alias3)]
4446-
07)------------AggregateExec: mode=FinalPartitioned, gby=[c1@0 as c1, alias1@1 as alias1], aggr=[sum(aggregate_test_100.c3) as alias2, max(aggregate_test_100.c4) as alias3]
4468+
06)----------AggregateExec: mode=Partial, gby=[c1@0 as c1], aggr=[count(alias1), min(alias2), sum(alias3), max(alias4)]
4469+
07)------------AggregateExec: mode=FinalPartitioned, gby=[c1@0 as c1, alias1@1 as alias1], aggr=[min(aggregate_test_100.c2) as alias2, sum(aggregate_test_100.c3) as alias3, max(aggregate_test_100.c4) as alias4]
44474470
08)--------------RepartitionExec: partitioning=Hash([c1@0, alias1@1], 8), input_partitions=8
4448-
09)----------------AggregateExec: mode=Partial, gby=[c1@0 as c1, c2@1 as alias1], aggr=[sum(aggregate_test_100.c3) as alias2, max(aggregate_test_100.c4) as alias3]
4471+
09)----------------AggregateExec: mode=Partial, gby=[c1@0 as c1, c2@1 as alias1], aggr=[min(aggregate_test_100.c2) as alias2, sum(aggregate_test_100.c3) as alias3, max(aggregate_test_100.c4) as alias4]
44494472
10)------------------RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
44504473
11)--------------------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/testing/data/csv/aggregate_test_100.csv]]}, projection=[c1, c2, c3, c4], file_type=csv, has_header=true
44514474

0 commit comments

Comments
 (0)