Skip to content

Commit df997f7

Browse files
authored
Optimize MIN/MAX over DISTINCT (#34699)
Fixes #34483
1 parent aa12ab0 commit df997f7

File tree

5 files changed

+30
-22
lines changed

5 files changed

+30
-22
lines changed

src/EFCore.Relational/Query/EnumerableExpression.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,12 @@ public virtual EnumerableExpression ApplySelector(Expression expression)
6868
=> new(expression, IsDistinct, Predicate, Orderings);
6969

7070
/// <summary>
71-
/// Applies DISTINCT operator to the selector of the <see cref="EnumerableExpression" />.
71+
/// Sets whether the DISTINCT operator should be applied to the selector
72+
/// of the <see cref="EnumerableExpression" />.
7273
/// </summary>
7374
/// <returns>The new expression with specified component updated.</returns>
74-
public virtual EnumerableExpression ApplyDistinct()
75-
=> new(Selector, distinct: true, Predicate, Orderings);
75+
public virtual EnumerableExpression SetDistinct(bool value)
76+
=> new(Selector, distinct: value, Predicate, Orderings);
7677

7778
/// <summary>
7879
/// Applies filter predicate to the <see cref="EnumerableExpression" />.

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,9 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK
954954
/// <inheritdoc />
955955
protected override ShapedQueryExpression? TranslateMax(ShapedQueryExpression source, LambdaExpression? selector, Type resultType)
956956
{
957+
var selectExpression = (SelectExpression)source.QueryExpression;
958+
selectExpression.IsDistinct = false;
959+
957960
// For Max() over an inline array, translate to GREATEST() if possible; otherwise use the default translation of aggregate SQL
958961
// MAX().
959962
// Note that some providers propagate NULL arguments (SQLite, MySQL), while others only return NULL if all arguments evaluate to
@@ -974,6 +977,9 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK
974977
/// <inheritdoc />
975978
protected override ShapedQueryExpression? TranslateMin(ShapedQueryExpression source, LambdaExpression? selector, Type resultType)
976979
{
980+
var selectExpression = (SelectExpression)source.QueryExpression;
981+
selectExpression.IsDistinct = false;
982+
977983
// See comments above in TranslateMax()
978984
if (TryExtractBareInlineCollectionValues(source, out var values)
979985
&& _sqlTranslator.GenerateLeast(values, resultType.UnwrapNullableType()) is SqlFunctionExpression leastExpression

src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,12 +1362,15 @@ protected virtual bool TryTranslateAggregateMethodCall(
13621362
{
13631363
switch (genericMethod.Name)
13641364
{
1365-
case nameof(Queryable.Average)
1366-
when QueryableMethods.IsAverageWithoutSelector(genericMethod):
13671365
case nameof(Queryable.Max)
13681366
when genericMethod == QueryableMethods.MaxWithoutSelector:
13691367
case nameof(Queryable.Min)
13701368
when genericMethod == QueryableMethods.MinWithoutSelector:
1369+
enumerableExpression = enumerableExpression.SetDistinct(false);
1370+
break;
1371+
1372+
case nameof(Queryable.Average)
1373+
when QueryableMethods.IsAverageWithoutSelector(genericMethod):
13711374
case nameof(Queryable.Sum)
13721375
when QueryableMethods.IsSumWithoutSelector(genericMethod):
13731376
case nameof(Queryable.Count)
@@ -1376,12 +1379,16 @@ when QueryableMethods.IsSumWithoutSelector(genericMethod):
13761379
when genericMethod == QueryableMethods.LongCountWithoutPredicate:
13771380
break;
13781381

1379-
case nameof(Queryable.Average)
1380-
when QueryableMethods.IsAverageWithSelector(genericMethod):
13811382
case nameof(Queryable.Max)
13821383
when genericMethod == QueryableMethods.MaxWithSelector:
13831384
case nameof(Queryable.Min)
13841385
when genericMethod == QueryableMethods.MinWithSelector:
1386+
enumerableExpression = enumerableExpression.SetDistinct(false);
1387+
enumerableExpression = ProcessSelector(enumerableExpression, arguments[1].UnwrapLambdaFromQuote());
1388+
break;
1389+
1390+
case nameof(Queryable.Average)
1391+
when QueryableMethods.IsAverageWithSelector(genericMethod):
13851392
case nameof(Queryable.Sum)
13861393
when QueryableMethods.IsSumWithSelector(genericMethod):
13871394
enumerableExpression = ProcessSelector(enumerableExpression, arguments[1].UnwrapLambdaFromQuote());
@@ -1478,7 +1485,7 @@ private bool TryTranslateAsEnumerableExpression(
14781485

14791486
if (!enumerableSource.IsDistinct)
14801487
{
1481-
enumerableExpression = enumerableSource.ApplyDistinct();
1488+
enumerableExpression = enumerableSource.SetDistinct(true);
14821489
return true;
14831490
}
14841491

test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,7 +2475,7 @@ public override async Task GroupBy_Select_Distinct_aggregate(bool async)
24752475

24762476
AssertSql(
24772477
"""
2478-
SELECT [o].[CustomerID] AS [Key], AVG(DISTINCT (CAST([o].[OrderID] AS float))) AS [Average], COUNT(DISTINCT ([o].[EmployeeID])) AS [Count], COUNT_BIG(DISTINCT ([o].[EmployeeID])) AS [LongCount], MAX(DISTINCT ([o].[OrderDate])) AS [Max], MIN(DISTINCT ([o].[OrderDate])) AS [Min], COALESCE(SUM(DISTINCT ([o].[OrderID])), 0) AS [Sum]
2478+
SELECT [o].[CustomerID] AS [Key], AVG(DISTINCT (CAST([o].[OrderID] AS float))) AS [Average], COUNT(DISTINCT ([o].[EmployeeID])) AS [Count], COUNT_BIG(DISTINCT ([o].[EmployeeID])) AS [LongCount], MAX([o].[OrderDate]) AS [Max], MIN([o].[OrderDate]) AS [Min], COALESCE(SUM(DISTINCT ([o].[OrderID])), 0) AS [Sum]
24792479
FROM [Orders] AS [o]
24802480
GROUP BY [o].[CustomerID]
24812481
""");
@@ -2487,7 +2487,7 @@ public override async Task GroupBy_group_Distinct_Select_Distinct_aggregate(bool
24872487

24882488
AssertSql(
24892489
"""
2490-
SELECT [o].[CustomerID] AS [Key], MAX(DISTINCT ([o].[OrderDate])) AS [Max]
2490+
SELECT [o].[CustomerID] AS [Key], MAX([o].[OrderDate]) AS [Max]
24912491
FROM [Orders] AS [o]
24922492
GROUP BY [o].[CustomerID]
24932493
""");
@@ -2499,9 +2499,9 @@ public override async Task GroupBy_group_Where_Select_Distinct_aggregate(bool as
24992499

25002500
AssertSql(
25012501
"""
2502-
SELECT [o].[CustomerID] AS [Key], MAX(DISTINCT (CASE
2502+
SELECT [o].[CustomerID] AS [Key], MAX(CASE
25032503
WHEN [o].[OrderDate] IS NOT NULL THEN [o].[OrderDate]
2504-
END)) AS [Max]
2504+
END) AS [Max]
25052505
FROM [Orders] AS [o]
25062506
GROUP BY [o].[CustomerID]
25072507
""");

test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4744,11 +4744,8 @@ public override async Task Select_distinct_max(bool async)
47444744

47454745
AssertSql(
47464746
"""
4747-
SELECT MAX([o0].[OrderID])
4748-
FROM (
4749-
SELECT DISTINCT [o].[OrderID]
4750-
FROM [Orders] AS [o]
4751-
) AS [o0]
4747+
SELECT MAX([o].[OrderID])
4748+
FROM [Orders] AS [o]
47524749
""");
47534750
}
47544751

@@ -4758,11 +4755,8 @@ public override async Task Select_distinct_min(bool async)
47584755

47594756
AssertSql(
47604757
"""
4761-
SELECT MIN([o0].[OrderID])
4762-
FROM (
4763-
SELECT DISTINCT [o].[OrderID]
4764-
FROM [Orders] AS [o]
4765-
) AS [o0]
4758+
SELECT MIN([o].[OrderID])
4759+
FROM [Orders] AS [o]
47664760
""");
47674761
}
47684762

0 commit comments

Comments
 (0)