Skip to content

Commit 3674278

Browse files
committed
Stop pushing down to subquery for non-Concat set operations without limit/offset
Closes dotnet#30684
1 parent 6e1ab07 commit 3674278

File tree

3 files changed

+125
-13
lines changed

3 files changed

+125
-13
lines changed

src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,23 +2280,30 @@ private void ApplySetOperation(SetOperationType setOperationType, SelectExpressi
22802280
var entityProjectionValueComparers = new List<ValueComparer>();
22812281
var otherExpressions = new List<SqlExpression>();
22822282

2283-
if (select1.Orderings.Count != 0
2284-
|| select1.Limit != null
2285-
|| select1.Offset != null)
2283+
// Push down into a subquery if limit/offset are defined. If not, any orderings can be discarded as set operations don't preserve
2284+
// them.
2285+
// Note that in some databases it may be possible to preserve the internal ordering of the set operands for Concat, but we don't
2286+
// currently support that.
2287+
if (select1.Limit != null || select1.Offset != null)
22862288
{
22872289
// If we are pushing down here, we need to make sure to assign unique alias to subquery also.
22882290
var subqueryAlias = GenerateUniqueAlias(_usedAliases, "t");
2289-
select1.PushdownIntoSubquery();
2291+
select1.PushdownIntoSubqueryInternal(liftOrderings: false);
22902292
select1._tables[0].Alias = subqueryAlias;
22912293
select1._tableReferences[0].Alias = subqueryAlias;
2294+
}
2295+
else
2296+
{
22922297
select1.ClearOrdering();
22932298
}
22942299

2295-
if (select2.Orderings.Count != 0
2296-
|| select2.Limit != null
2297-
|| select2.Offset != null)
2300+
// Do the same for the other side of the set operation
2301+
if (select2.Limit != null || select2.Offset != null)
2302+
{
2303+
select2.PushdownIntoSubqueryInternal(liftOrderings: false);
2304+
}
2305+
else
22982306
{
2299-
select2.PushdownIntoSubquery();
23002307
select2.ClearOrdering();
23012308
}
23022309

@@ -3572,7 +3579,11 @@ public Expression AddOuterApply(
35723579
public void PushdownIntoSubquery()
35733580
=> PushdownIntoSubqueryInternal();
35743581

3575-
private SqlRemappingVisitor PushdownIntoSubqueryInternal()
3582+
/// <summary>
3583+
/// Pushes down the <see cref="SelectExpression" /> into a subquery.
3584+
/// </summary>
3585+
/// <param name="liftOrderings">Whether orderings on the query should be lifted out of the subquery.</param>
3586+
private SqlRemappingVisitor PushdownIntoSubqueryInternal(bool liftOrderings = true)
35763587
{
35773588
var subqueryAlias = GenerateUniqueAlias(_usedAliases, "t");
35783589
var subquery = new SelectExpression(
@@ -3736,13 +3747,14 @@ private SqlRemappingVisitor PushdownIntoSubqueryInternal()
37363747
foreach (var ordering in subquery._orderings)
37373748
{
37383749
var orderingExpression = ordering.Expression;
3739-
if (projectionMap.TryGetValue(orderingExpression, out var outerColumn))
3750+
if (liftOrderings && projectionMap.TryGetValue(orderingExpression, out var outerColumn))
37403751
{
37413752
_orderings.Add(ordering.Update(outerColumn));
37423753
}
3743-
else if (!IsDistinct
3744-
&& GroupBy.Count == 0
3745-
|| GroupBy.Contains(orderingExpression))
3754+
else if (liftOrderings
3755+
&& (!IsDistinct
3756+
&& GroupBy.Count == 0
3757+
|| GroupBy.Contains(orderingExpression)))
37463758
{
37473759
_orderings.Add(
37483760
ordering.Update(

test/EFCore.Specification.Tests/Query/NorthwindSetOperationsQueryTestBase.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,38 @@ public virtual Task Union_over_scalarsubquery_scalarsubquery(bool async)
761761
ss => ss.Set<Order>().Select(o => o.OrderDetails.Count())
762762
.Union(ss.Set<Order>().Select(o => o.OrderDetails.Count())));
763763

764+
[ConditionalTheory]
765+
[MemberData(nameof(IsAsyncData))]
766+
public virtual Task Union_over_OrderBy_Take1(bool async)
767+
=> AssertQueryScalar(
768+
async,
769+
ss => ss.Set<Order>().OrderBy(o => o.OrderDate).Take(5).Select(o => o.OrderID)
770+
.Union(ss.Set<Order>().Select(o => o.OrderID)));
771+
772+
[ConditionalTheory]
773+
[MemberData(nameof(IsAsyncData))]
774+
public virtual Task Union_over_OrderBy_without_Skip_Take1(bool async)
775+
=> AssertQueryScalar(
776+
async,
777+
ss => ss.Set<Order>().OrderBy(o => o.OrderDate).Select(o => o.OrderID)
778+
.Union(ss.Set<Order>().Select(o => o.OrderID)));
779+
780+
[ConditionalTheory]
781+
[MemberData(nameof(IsAsyncData))]
782+
public virtual Task Union_over_OrderBy_Take2(bool async)
783+
=> AssertQueryScalar(
784+
async,
785+
ss => ss.Set<Order>().Select(o => o.OrderID)
786+
.Union(ss.Set<Order>().OrderBy(o => o.OrderDate).Take(5).Select(o => o.OrderID)));
787+
788+
[ConditionalTheory]
789+
[MemberData(nameof(IsAsyncData))]
790+
public virtual Task Union_over_OrderBy_without_Skip_Take2(bool async)
791+
=> AssertQueryScalar(
792+
async,
793+
ss => ss.Set<Order>().Select(o => o.OrderID)
794+
.Union(ss.Set<Order>().OrderBy(o => o.OrderDate).Select(o => o.OrderID)));
795+
764796
[ConditionalTheory]
765797
[MemberData(nameof(IsAsyncData))]
766798
public virtual Task OrderBy_Take_Union(bool async)

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,74 @@ FROM [Orders] AS [o1]
10201020
""");
10211021
}
10221022

1023+
public override async Task Union_over_OrderBy_Take1(bool async)
1024+
{
1025+
await base.Union_over_OrderBy_Take1(async);
1026+
1027+
AssertSql(
1028+
"""
1029+
@__p_0='5'
1030+
1031+
SELECT [t].[OrderID]
1032+
FROM (
1033+
SELECT TOP(@__p_0) [o].[OrderID]
1034+
FROM [Orders] AS [o]
1035+
ORDER BY [o].[OrderDate]
1036+
) AS [t]
1037+
UNION
1038+
SELECT [o0].[OrderID]
1039+
FROM [Orders] AS [o0]
1040+
""");
1041+
}
1042+
1043+
public override async Task Union_over_OrderBy_without_Skip_Take1(bool async)
1044+
{
1045+
await base.Union_over_OrderBy_without_Skip_Take1(async);
1046+
1047+
AssertSql(
1048+
"""
1049+
SELECT [o].[OrderID]
1050+
FROM [Orders] AS [o]
1051+
UNION
1052+
SELECT [o0].[OrderID]
1053+
FROM [Orders] AS [o0]
1054+
""");
1055+
}
1056+
1057+
public override async Task Union_over_OrderBy_Take2(bool async)
1058+
{
1059+
await base.Union_over_OrderBy_Take2(async);
1060+
1061+
AssertSql(
1062+
"""
1063+
@__p_0='5'
1064+
1065+
SELECT [o].[OrderID]
1066+
FROM [Orders] AS [o]
1067+
UNION
1068+
SELECT [t0].[OrderID]
1069+
FROM (
1070+
SELECT TOP(@__p_0) [o0].[OrderID]
1071+
FROM [Orders] AS [o0]
1072+
ORDER BY [o0].[OrderDate]
1073+
) AS [t0]
1074+
""");
1075+
}
1076+
1077+
public override async Task Union_over_OrderBy_without_Skip_Take2(bool async)
1078+
{
1079+
await base.Union_over_OrderBy_without_Skip_Take2(async);
1080+
1081+
AssertSql(
1082+
"""
1083+
SELECT [o].[OrderID]
1084+
FROM [Orders] AS [o]
1085+
UNION
1086+
SELECT [o0].[OrderID]
1087+
FROM [Orders] AS [o0]
1088+
""");
1089+
}
1090+
10231091
public override async Task OrderBy_Take_Union(bool async)
10241092
{
10251093
await base.OrderBy_Take_Union(async);

0 commit comments

Comments
 (0)