Skip to content

Commit 9ffb26a

Browse files
authored
SQL: Remove restriction for single column grouping (#31818)
For historical reasons SQL restricts GROUP BY to only one field. This commit removes the restriction and improves the test suite with multi group by tests. Close #31793
1 parent 994a251 commit 9ffb26a

File tree

8 files changed

+197
-76
lines changed

8 files changed

+197
-76
lines changed

docs/reference/sql/language/syntax/select.asciidoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ And grouping by column expression (typically used along-side an alias):
177177
include-tagged::{sql-specs}/docs.csv-spec[groupByExpression]
178178
----
179179

180+
Or a mixture of the above:
181+
["source","sql",subs="attributes,callouts,macros"]
182+
----
183+
include-tagged::{sql-specs}/docs.csv-spec[groupByMulti]
184+
----
185+
186+
180187
When a `GROUP BY` clause is used in a `SELECT`, _all_ output expressions must be either aggregate functions or expressions used for grouping or derivatives of (otherwise there would be more than one possible value to return for each ungrouped column).
181188

182189
To wit:

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ public static Attribute attribute(Expression e) {
104104
return null;
105105
}
106106

107+
public static boolean equalsAsAttribute(Expression left, Expression right) {
108+
if (!left.semanticEquals(right)) {
109+
Attribute l = attribute(left);
110+
return (l != null && l.semanticEquals(attribute(right)));
111+
}
112+
return true;
113+
}
114+
107115
public static TypeResolution typeMustBe(Expression e, Predicate<Expression> predicate, String message) {
108116
return predicate.test(e) ? TypeResolution.TYPE_RESOLVED : new TypeResolution(message);
109117
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
112112
new ReplaceAggsWithStats(),
113113
new PromoteStatsToExtendedStats(),
114114
new ReplaceAggsWithPercentiles(),
115-
new ReplceAggsWithPercentileRanks()
115+
new ReplaceAggsWithPercentileRanks()
116116
);
117117

118118
Batch operators = new Batch("Operator Optimization",
@@ -132,7 +132,9 @@ protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
132132
new PruneFilters(),
133133
new PruneOrderBy(),
134134
new PruneOrderByNestedFields(),
135-
new PruneCast()
135+
new PruneCast(),
136+
// order by alignment of the aggs
137+
new SortAggregateOnOrderBy()
136138
// requires changes in the folding
137139
// since the exact same function, with the same ID can appear in multiple places
138140
// see https://github.com/elastic/x-pack-elasticsearch/issues/3527
@@ -612,7 +614,7 @@ protected LogicalPlan rule(LogicalPlan e) {
612614
}
613615
}
614616

615-
static class ReplceAggsWithPercentileRanks extends Rule<LogicalPlan, LogicalPlan> {
617+
static class ReplaceAggsWithPercentileRanks extends Rule<LogicalPlan, LogicalPlan> {
616618

617619
@Override
618620
public LogicalPlan apply(LogicalPlan p) {
@@ -828,6 +830,46 @@ protected LogicalPlan rule(OrderBy ob) {
828830
}
829831
}
830832

833+
/**
834+
* Align the order in aggregate based on the order by.
835+
*/
836+
static class SortAggregateOnOrderBy extends OptimizerRule<OrderBy> {
837+
838+
@Override
839+
protected LogicalPlan rule(OrderBy ob) {
840+
List<Order> order = ob.order();
841+
842+
// remove constants
843+
List<Order> nonConstant = order.stream().filter(o -> !o.child().foldable()).collect(toList());
844+
845+
// if the sort points to an agg, change the agg order based on the order
846+
if (ob.child() instanceof Aggregate) {
847+
Aggregate a = (Aggregate) ob.child();
848+
List<Expression> groupings = new ArrayList<>(a.groupings());
849+
boolean orderChanged = false;
850+
851+
for (int orderIndex = 0; orderIndex < nonConstant.size(); orderIndex++) {
852+
Order o = nonConstant.get(orderIndex);
853+
Expression fieldToOrder = o.child();
854+
for (Expression group : a.groupings()) {
855+
if (Expressions.equalsAsAttribute(fieldToOrder, group)) {
856+
// move grouping in front
857+
groupings.remove(group);
858+
groupings.add(orderIndex, group);
859+
orderChanged = true;
860+
}
861+
}
862+
}
863+
864+
if (orderChanged) {
865+
Aggregate newAgg = new Aggregate(a.location(), a.child(), groupings, a.aggregates());
866+
return new OrderBy(ob.location(), newAgg, ob.order());
867+
}
868+
}
869+
return ob;
870+
}
871+
}
872+
831873
static class CombineLimits extends OptimizerRule<Limit> {
832874

833875
@Override

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/Verifier.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
*/
66
package org.elasticsearch.xpack.sql.planner;
77

8-
import org.elasticsearch.xpack.sql.expression.Expressions;
9-
import org.elasticsearch.xpack.sql.plan.physical.AggregateExec;
108
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
119
import org.elasticsearch.xpack.sql.plan.physical.Unexecutable;
1210
import org.elasticsearch.xpack.sql.plan.physical.UnplannedExec;
@@ -71,23 +69,11 @@ static List<Failure> verifyMappingPlan(PhysicalPlan plan) {
7169
failures.add(fail(e, "Unresolved expression"));
7270
}
7371
});
74-
75-
if (p instanceof AggregateExec) {
76-
forbidMultiFieldGroupBy((AggregateExec) p, failures);
77-
}
7872
});
7973

8074
return failures;
8175
}
8276

83-
private static void forbidMultiFieldGroupBy(AggregateExec a, List<Failure> failures) {
84-
if (a.groupings().size() > 1) {
85-
failures.add(fail(a.groupings().get(0), "Currently, only a single expression can be used with GROUP BY; please select one of "
86-
+ Expressions.names(a.groupings())));
87-
}
88-
}
89-
90-
9177
static List<Failure> verifyExecutingPlan(PhysicalPlan plan) {
9278
List<Failure> failures = new ArrayList<>();
9379

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/VerifierErrorMessagesTests.java

Lines changed: 0 additions & 52 deletions
This file was deleted.

x-pack/qa/sql/no-security/src/test/java/org/elasticsearch/xpack/qa/sql/nosecurity/JdbcDocCsvSpectIT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import org.elasticsearch.client.RestClient;
1212
import org.elasticsearch.xpack.qa.sql.jdbc.CsvTestUtils.CsvTestCase;
1313
import org.elasticsearch.xpack.qa.sql.jdbc.DataLoader;
14-
import org.elasticsearch.xpack.qa.sql.jdbc.JdbcAssert;
14+
import org.elasticsearch.xpack.qa.sql.jdbc.JdbcTestUtils;
1515
import org.elasticsearch.xpack.qa.sql.jdbc.SpecBaseIntegrationTestCase;
1616
import org.elasticsearch.xpack.qa.sql.jdbc.SqlSpecTestCase;
1717

@@ -68,8 +68,8 @@ protected void assertResults(ResultSet expected, ResultSet elastic) throws SQLEx
6868
//
6969
// uncomment this to printout the result set and create new CSV tests
7070
//
71-
//JdbcTestUtils.logLikeCLI(elastic, log);
72-
JdbcAssert.assertResultSets(expected, elastic, log, true);
71+
JdbcTestUtils.logLikeCLI(elastic, log);
72+
//JdbcAssert.assertResultSets(expected, elastic, log, true);
7373
}
7474

7575
@Override

0 commit comments

Comments
 (0)