From 9ffb26ab02d7ea550d14f686b007be76f3149626 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Fri, 6 Jul 2018 20:55:27 +0300 Subject: [PATCH] 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 --- .../sql/language/syntax/select.asciidoc | 7 ++ .../xpack/sql/expression/Expressions.java | 8 ++ .../xpack/sql/optimizer/Optimizer.java | 48 ++++++- .../xpack/sql/planner/Verifier.java | 14 --- .../planner/VerifierErrorMessagesTests.java | 52 -------- .../qa/sql/nosecurity/JdbcDocCsvSpectIT.java | 6 +- x-pack/qa/sql/src/main/resources/agg.sql-spec | 117 +++++++++++++++++- .../qa/sql/src/main/resources/docs.csv-spec | 21 ++++ 8 files changed, 197 insertions(+), 76 deletions(-) delete mode 100644 x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/VerifierErrorMessagesTests.java diff --git a/docs/reference/sql/language/syntax/select.asciidoc b/docs/reference/sql/language/syntax/select.asciidoc index b58173097b0..67291dee7a6 100644 --- a/docs/reference/sql/language/syntax/select.asciidoc +++ b/docs/reference/sql/language/syntax/select.asciidoc @@ -177,6 +177,13 @@ And grouping by column expression (typically used along-side an alias): include-tagged::{sql-specs}/docs.csv-spec[groupByExpression] ---- +Or a mixture of the above: +["source","sql",subs="attributes,callouts,macros"] +---- +include-tagged::{sql-specs}/docs.csv-spec[groupByMulti] +---- + + 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). To wit: diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java index f3106cfc3c4..5851e991314 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Expressions.java @@ -104,6 +104,14 @@ public abstract class Expressions { return null; } + public static boolean equalsAsAttribute(Expression left, Expression right) { + if (!left.semanticEquals(right)) { + Attribute l = attribute(left); + return (l != null && l.semanticEquals(attribute(right))); + } + return true; + } + public static TypeResolution typeMustBe(Expression e, Predicate predicate, String message) { return predicate.test(e) ? TypeResolution.TYPE_RESOLVED : new TypeResolution(message); } diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java index de4c0644b79..55c4112d38b 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java @@ -112,7 +112,7 @@ public class Optimizer extends RuleExecutor { new ReplaceAggsWithStats(), new PromoteStatsToExtendedStats(), new ReplaceAggsWithPercentiles(), - new ReplceAggsWithPercentileRanks() + new ReplaceAggsWithPercentileRanks() ); Batch operators = new Batch("Operator Optimization", @@ -132,7 +132,9 @@ public class Optimizer extends RuleExecutor { new PruneFilters(), new PruneOrderBy(), new PruneOrderByNestedFields(), - new PruneCast() + new PruneCast(), + // order by alignment of the aggs + new SortAggregateOnOrderBy() // requires changes in the folding // since the exact same function, with the same ID can appear in multiple places // see https://github.com/elastic/x-pack-elasticsearch/issues/3527 @@ -612,7 +614,7 @@ public class Optimizer extends RuleExecutor { } } - static class ReplceAggsWithPercentileRanks extends Rule { + static class ReplaceAggsWithPercentileRanks extends Rule { @Override public LogicalPlan apply(LogicalPlan p) { @@ -828,6 +830,46 @@ public class Optimizer extends RuleExecutor { } } + /** + * Align the order in aggregate based on the order by. + */ + static class SortAggregateOnOrderBy extends OptimizerRule { + + @Override + protected LogicalPlan rule(OrderBy ob) { + List order = ob.order(); + + // remove constants + List nonConstant = order.stream().filter(o -> !o.child().foldable()).collect(toList()); + + // if the sort points to an agg, change the agg order based on the order + if (ob.child() instanceof Aggregate) { + Aggregate a = (Aggregate) ob.child(); + List groupings = new ArrayList<>(a.groupings()); + boolean orderChanged = false; + + for (int orderIndex = 0; orderIndex < nonConstant.size(); orderIndex++) { + Order o = nonConstant.get(orderIndex); + Expression fieldToOrder = o.child(); + for (Expression group : a.groupings()) { + if (Expressions.equalsAsAttribute(fieldToOrder, group)) { + // move grouping in front + groupings.remove(group); + groupings.add(orderIndex, group); + orderChanged = true; + } + } + } + + if (orderChanged) { + Aggregate newAgg = new Aggregate(a.location(), a.child(), groupings, a.aggregates()); + return new OrderBy(ob.location(), newAgg, ob.order()); + } + } + return ob; + } + } + static class CombineLimits extends OptimizerRule { @Override diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/Verifier.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/Verifier.java index 0aef7ccaf8e..1e527657ae0 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/Verifier.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/Verifier.java @@ -5,8 +5,6 @@ */ package org.elasticsearch.xpack.sql.planner; -import org.elasticsearch.xpack.sql.expression.Expressions; -import org.elasticsearch.xpack.sql.plan.physical.AggregateExec; import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan; import org.elasticsearch.xpack.sql.plan.physical.Unexecutable; import org.elasticsearch.xpack.sql.plan.physical.UnplannedExec; @@ -71,23 +69,11 @@ abstract class Verifier { failures.add(fail(e, "Unresolved expression")); } }); - - if (p instanceof AggregateExec) { - forbidMultiFieldGroupBy((AggregateExec) p, failures); - } }); return failures; } - private static void forbidMultiFieldGroupBy(AggregateExec a, List failures) { - if (a.groupings().size() > 1) { - failures.add(fail(a.groupings().get(0), "Currently, only a single expression can be used with GROUP BY; please select one of " - + Expressions.names(a.groupings()))); - } - } - - static List verifyExecutingPlan(PhysicalPlan plan) { List failures = new ArrayList<>(); diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/VerifierErrorMessagesTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/VerifierErrorMessagesTests.java deleted file mode 100644 index 154885261fd..00000000000 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/VerifierErrorMessagesTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.sql.planner; - -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.sql.analysis.analyzer.Analyzer; -import org.elasticsearch.xpack.sql.analysis.index.EsIndex; -import org.elasticsearch.xpack.sql.analysis.index.IndexResolution; -import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry; -import org.elasticsearch.xpack.sql.optimizer.Optimizer; -import org.elasticsearch.xpack.sql.parser.SqlParser; -import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan; -import org.elasticsearch.xpack.sql.type.DataType; -import org.elasticsearch.xpack.sql.type.EsField; -import org.elasticsearch.xpack.sql.type.KeywordEsField; -import org.elasticsearch.xpack.sql.type.TextEsField; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.TimeZone; - -public class VerifierErrorMessagesTests extends ESTestCase { - - private SqlParser parser = new SqlParser(); - private Optimizer optimizer = new Optimizer(); - private Planner planner = new Planner(); - - private String verify(String sql) { - Map mapping = new LinkedHashMap<>(); - mapping.put("bool", new EsField("bool", DataType.BOOLEAN, Collections.emptyMap(), true)); - mapping.put("int", new EsField("int", DataType.INTEGER, Collections.emptyMap(), true)); - mapping.put("text", new TextEsField("text", Collections.emptyMap(), true)); - mapping.put("keyword", new KeywordEsField("keyword", Collections.emptyMap(), true, DataType.KEYWORD.defaultPrecision, true)); - EsIndex test = new EsIndex("test", mapping); - IndexResolution getIndexResult = IndexResolution.valid(test); - Analyzer analyzer = new Analyzer(new FunctionRegistry(), getIndexResult, TimeZone.getTimeZone("UTC")); - LogicalPlan plan = optimizer.optimize(analyzer.analyze(parser.createStatement(sql), true)); - PlanningException e = expectThrows(PlanningException.class, () -> planner.mapPlan(plan, true)); - assertTrue(e.getMessage().startsWith("Found ")); - String header = "Found 1 problem(s)\nline "; - return e.getMessage().substring(header.length()); - } - - public void testMultiGroupBy() { - assertEquals("1:32: Currently, only a single expression can be used with GROUP BY; please select one of [bool, keyword]", - verify("SELECT bool FROM test GROUP BY bool, keyword")); - } -} diff --git a/x-pack/qa/sql/no-security/src/test/java/org/elasticsearch/xpack/qa/sql/nosecurity/JdbcDocCsvSpectIT.java b/x-pack/qa/sql/no-security/src/test/java/org/elasticsearch/xpack/qa/sql/nosecurity/JdbcDocCsvSpectIT.java index 24e8c170cc3..2c99c8b5383 100644 --- a/x-pack/qa/sql/no-security/src/test/java/org/elasticsearch/xpack/qa/sql/nosecurity/JdbcDocCsvSpectIT.java +++ b/x-pack/qa/sql/no-security/src/test/java/org/elasticsearch/xpack/qa/sql/nosecurity/JdbcDocCsvSpectIT.java @@ -11,7 +11,7 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.client.RestClient; import org.elasticsearch.xpack.qa.sql.jdbc.CsvTestUtils.CsvTestCase; import org.elasticsearch.xpack.qa.sql.jdbc.DataLoader; -import org.elasticsearch.xpack.qa.sql.jdbc.JdbcAssert; +import org.elasticsearch.xpack.qa.sql.jdbc.JdbcTestUtils; import org.elasticsearch.xpack.qa.sql.jdbc.SpecBaseIntegrationTestCase; import org.elasticsearch.xpack.qa.sql.jdbc.SqlSpecTestCase; @@ -68,8 +68,8 @@ public class JdbcDocCsvSpectIT extends SpecBaseIntegrationTestCase { // // uncomment this to printout the result set and create new CSV tests // - //JdbcTestUtils.logLikeCLI(elastic, log); - JdbcAssert.assertResultSets(expected, elastic, log, true); + JdbcTestUtils.logLikeCLI(elastic, log); + //JdbcAssert.assertResultSets(expected, elastic, log, true); } @Override diff --git a/x-pack/qa/sql/src/main/resources/agg.sql-spec b/x-pack/qa/sql/src/main/resources/agg.sql-spec index 3e1c38c337d..f42ce0ef7a0 100644 --- a/x-pack/qa/sql/src/main/resources/agg.sql-spec +++ b/x-pack/qa/sql/src/main/resources/agg.sql-spec @@ -48,6 +48,39 @@ SELECT emp_no * 2 AS e FROM test_emp GROUP BY e ORDER BY e; groupByModScalar SELECT (emp_no % 3) + 1 AS e FROM test_emp GROUP BY e ORDER BY e; +// multiple group by +groupByMultiOnText +SELECT gender g, languages l FROM "test_emp" GROUP BY gender, languages ORDER BY gender ASC, languages ASC; +groupByMultiOnTextWithWhereClause +SELECT gender g, languages l FROM "test_emp" WHERE emp_no < 10020 GROUP BY gender, languages ORDER BY gender ASC, languages ASC; +groupByMultiOnTextWithWhereAndLimit +SELECT gender g, languages l FROM "test_emp" WHERE emp_no < 10020 GROUP BY gender, languages ORDER BY gender, languages LIMIT 1; +groupByMultiOnTextOnAlias +SELECT gender g, languages l FROM "test_emp" WHERE emp_no < 10020 GROUP BY g, l ORDER BY gender ASC, languages ASC; +groupByMultiOnTextOnAliasOrderDesc +SELECT gender g, languages l FROM "test_emp" WHERE emp_no < 10020 GROUP BY g, l ORDER BY g, l ASC; + +groupByMultiOnDate +SELECT birth_date b, languages l FROM "test_emp" GROUP BY birth_date, languages ORDER BY birth_date DESC, languages; +groupByMultiOnDateWithWhereClause +SELECT birth_date b, languages l FROM "test_emp" WHERE emp_no < 10020 GROUP BY birth_date, languages ORDER BY birth_date DESC, languages; +groupByMultiOnDateWithWhereAndLimit +SELECT birth_date b, languages l FROM "test_emp" WHERE emp_no < 10020 GROUP BY birth_date, languages ORDER BY birth_date DESC, languages LIMIT 1; +groupByMultiOnDateOnAlias +SELECT birth_date b, languages l FROM "test_emp" WHERE emp_no < 10020 GROUP BY b, l ORDER BY birth_date DESC, languages; + +groupByMultiAddScalar +SELECT emp_no + 1 AS e, languages + 5 AS l FROM test_emp GROUP BY e, l ORDER BY e, l; +groupByMultiMinScalarDesc +SELECT emp_no - 1 AS e, languages - 5 AS l FROM test_emp GROUP BY e, l ORDER BY e DESC, l; +groupByMultiAddScalarDesc +SELECT emp_no % 2 AS e, languages % 10 AS l FROM test_emp GROUP BY e, l ORDER BY e DESC, l; +groupByMultiMulScalar +SELECT emp_no * 2 AS e, languages * 2 AS l FROM test_emp GROUP BY e, l ORDER BY e, l; +groupByMultiModScalar +SELECT (emp_no % 3) + 1 AS e, (languages % 3) + 1 AS l FROM test_emp GROUP BY e, l ORDER BY e, l; + + // // Aggregate Functions // @@ -76,6 +109,18 @@ countDistinct SELECT COUNT(DISTINCT hire_date) AS count FROM test_emp; // end::countDistinct +aggCountAliasAndWhereClauseMultiGroupBy +SELECT gender g, languages l, COUNT(*) c FROM "test_emp" WHERE emp_no < 10020 GROUP BY gender, languages ORDER BY gender, languages; +aggCountAliasAndWhereClauseAndLimitMultiGroupBy +SELECT gender g, languages l, COUNT(*) c FROM "test_emp" WHERE emp_no < 10020 GROUP BY gender, languages ORDER BY gender, languages LIMIT 1; +aggCountAliasWithCastAndFilterMultiGroupBy +SELECT gender g, languages l, CAST(COUNT(*) AS INT) c FROM "test_emp" WHERE emp_no < 10020 GROUP BY gender, languages ORDER BY gender, languages; +aggCountWithAliasMultiGroupBy +SELECT gender g, languages l, COUNT(*) c FROM "test_emp" GROUP BY g, l ORDER BY gender, languages; +aggCountWithAliasMultiGroupByDifferentOrder +SELECT gender g, languages l, COUNT(*) c FROM "test_emp" GROUP BY g, l ORDER BY languages ASC, gender DESC; + + // Conditional COUNT aggCountAndHaving @@ -84,7 +129,6 @@ aggCountAndHavingEquality SELECT gender g, COUNT(*) c FROM "test_emp" GROUP BY g HAVING COUNT(*) = 10 ORDER BY gender; aggCountOnColumnAndHaving SELECT gender g, COUNT(gender) c FROM "test_emp" GROUP BY g HAVING COUNT(gender) > 10 ORDER BY gender; -// NOT supported yet since Having introduces a new agg aggCountOnColumnAndWildcardAndHaving SELECT gender g, COUNT(*) c FROM "test_emp" GROUP BY g HAVING COUNT(gender) > 10 ORDER BY gender; aggCountAndHavingOnAlias @@ -101,15 +145,41 @@ aggCountOnColumnAndHavingBetween SELECT gender g, COUNT(gender) c FROM "test_emp" GROUP BY g HAVING c BETWEEN 10 AND 70 ORDER BY gender; aggCountOnColumnAndHavingBetweenWithLimit SELECT gender g, COUNT(gender) c FROM "test_emp" GROUP BY g HAVING c BETWEEN 10 AND 70 ORDER BY gender LIMIT 1; - aggCountOnColumnAndHavingOnAliasAndFunction SELECT gender g, COUNT(gender) c FROM "test_emp" GROUP BY g HAVING c > 10 AND COUNT(gender) < 70 ORDER BY gender; -// NOT supported yet since Having introduces a new agg aggCountOnColumnAndHavingOnAliasAndFunctionWildcard -> COUNT(*/1) vs COUNT(gender) SELECT gender g, COUNT(gender) c FROM "test_emp" GROUP BY g HAVING c > 10 AND COUNT(*) < 70 ORDER BY gender; aggCountOnColumnAndHavingOnAliasAndFunctionConstant SELECT gender g, COUNT(gender) c FROM "test_emp" GROUP BY g HAVING c > 10 AND COUNT(1) < 70 ORDER BY gender; +aggCountAndHavingMultiGroupBy +SELECT gender g, languages l, COUNT(*) c FROM "test_emp" GROUP BY g, l HAVING COUNT(*) > 10 ORDER BY gender, l; +aggCountOnColumnAndHavingMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING COUNT(gender) > 10 ORDER BY gender, languages; +aggCountOnSecondColumnAndHavingMultiGroupBy +SELECT gender g, languages l, COUNT(languages) c FROM "test_emp" GROUP BY g, l HAVING COUNT(gender) > 10 ORDER BY gender, languages; +aggCountOnColumnAndWildcardAndHavingMultiGroupBy +SELECT gender g, languages l, COUNT(*) c FROM "test_emp" GROUP BY g, l HAVING COUNT(gender) > 10 ORDER BY gender, languages; +aggCountAndHavingOnAliasMultiGroupBy +SELECT gender g, languages l, COUNT(*) c FROM "test_emp" GROUP BY g, l HAVING c > 10 ORDER BY gender, languages; +aggCountOnColumnAndHavingOnAliasMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c > 10 ORDER BY gender, languages; +aggCountOnColumnAndMultipleHavingMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c > 10 AND c < 70 ORDER BY gender, languages; +aggCountOnColumnAndMultipleHavingWithLimitMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c > 10 AND c < 70 ORDER BY gender, languages LIMIT 1; +aggCountOnColumnAndHavingBetweenMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c BETWEEN 10 AND 70 ORDER BY gender, languages; +aggCountOnColumnAndHavingBetweenWithLimitMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c BETWEEN 10 AND 70 ORDER BY gender, languages LIMIT 1; + +aggCountOnColumnAndHavingOnAliasAndFunctionMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c > 10 AND COUNT(gender) < 70 ORDER BY gender, languages; +aggCountOnColumnAndHavingOnAliasAndFunctionWildcardMultiGroupBy -> COUNT(*/1) vs COUNT(gender) +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c > 10 AND COUNT(*) < 70 ORDER BY gender, languages; +aggCountOnColumnAndHavingOnAliasAndFunctionConstantMultiGroupBy +SELECT gender g, languages l, COUNT(gender) c FROM "test_emp" GROUP BY g, l HAVING c > 10 AND COUNT(1) < 70 ORDER BY gender, languages; + // MIN aggMinImplicit @@ -149,6 +219,21 @@ SELECT gender g, MIN(emp_no) m FROM "test_emp" GROUP BY g HAVING m BETWEEN 10 AN aggMinWithMultipleHavingOnAliasAndFunction SELECT gender g, MIN(emp_no) m FROM "test_emp" GROUP BY g HAVING m > 10 AND MIN(emp_no) < 99999 ORDER BY gender; +aggMinWithHavingGroupMultiGroupBy +SELECT gender g, languages l, MIN(emp_no) m FROM "test_emp" GROUP BY g, l HAVING MIN(emp_no) > 10 ORDER BY gender, languages; +aggMinWithHavingOnAliasMultiGroupBy +SELECT gender g, languages l, MIN(emp_no) m FROM "test_emp" GROUP BY g, l HAVING m > 10 ORDER BY gender, languages; +aggMinWithMultipleHavingMultiGroupBy +SELECT gender g, languages l, MIN(emp_no) m FROM "test_emp" GROUP BY g, l HAVING m > 10 AND m < 99999 ORDER BY gender, languages; +aggMinWithMultipleHavingBetweenMultiGroupBy +SELECT gender g, languages l, MIN(emp_no) m FROM "test_emp" GROUP BY g, l HAVING m BETWEEN 10 AND 99999 ORDER BY gender, languages; +aggMinWithMultipleHavingWithLimitMultiGroupBy +SELECT gender g, languages l, MIN(emp_no) m FROM "test_emp" GROUP BY g, l HAVING m > 10 AND m < 99999 ORDER BY g, l LIMIT 1; +aggMinWithMultipleHavingBetweenMultiGroupBy +SELECT gender g, languages l, MIN(emp_no) m FROM "test_emp" GROUP BY g, l HAVING m BETWEEN 10 AND 99999 ORDER BY g, l LIMIT 1; +aggMinWithMultipleHavingOnAliasAndFunctionMultiGroupBy +SELECT gender g, languages l, MIN(emp_no) m FROM "test_emp" GROUP BY g, l HAVING m > 10 AND MIN(emp_no) < 99999 ORDER BY gender, languages; + // MAX aggMaxImplicit @@ -258,6 +343,8 @@ SELECT gender g, CAST(AVG(emp_no) AS FLOAT) a FROM "test_emp" GROUP BY g HAVING // aggGroupByOnScalarWithHaving SELECT emp_no + 1 AS e FROM test_emp GROUP BY e HAVING AVG(salary) BETWEEN 1 AND 10010 ORDER BY e; +aggMultiGroupByOnScalarWithHaving +SELECT emp_no + 1 AS e, languages % 10 AS l FROM test_emp GROUP BY e, l HAVING AVG(salary) BETWEEN 1 AND 10010 ORDER BY e, l; // // Mixture of Aggs that triggers promotion of aggs to stats @@ -277,12 +364,34 @@ SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_ aggHavingScalarOnAggFunctionsWithoutAliasesInAndNotInGroupBy SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY languages HAVING MAX(salary) % MIN(salary) + AVG(salary) > 3000 ORDER BY languages; +aggMultiGroupByMultiIncludingScalarFunction +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages ORDER BY gender, languages; +aggMultiGroupByHavingWithAggNotInGroupBy +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING AVG(salary) > 30000 ORDER BY gender, languages; +aggMultiGroupByHavingWithAliasOnScalarFromGroupBy +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING d BETWEEN 50 AND 10000 AND AVG(salary) > 30000 ORDER BY gender, languages; +aggMultiGroupByHavingWithScalarFunctionBasedOnAliasFromGroupBy +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING ma % mi > 1 AND AVG(salary) > 30000 ORDER BY gender, languages; +aggMultiGroupByHavingWithMultipleScalarFunctionsBasedOnAliasFromGroupBy +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING d - ma % mi > 0 AND AVG(salary) > 30000 ORDER BY gender, languages; +aggMultiGroupByHavingWithMultipleScalarFunctionsBasedOnAliasFromGroupByAndAggNotInGroupBy +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING ROUND(d - ABS(ma % mi)) + AVG(salary) > 0 AND AVG(salary) > 30000 ORDER BY gender, languages; +aggMultiGroupByHavingScalarOnAggFunctionsWithoutAliasesInAndNotInGroupBy +SELECT MIN(salary) mi, MAX(salary) ma, MAX(salary) - MIN(salary) AS d FROM test_emp GROUP BY gender, languages HAVING MAX(salary) % MIN(salary) + AVG(salary) > 3000 ORDER BY gender, languages; + + // // Mixture of aggs that get promoted plus filtering on one of them // aggMultiWithHaving SELECT MIN(salary) min, MAX(salary) max, gender g, COUNT(*) c FROM "test_emp" WHERE languages > 0 GROUP BY g HAVING max > 74600 ORDER BY gender; +aggMultiGroupByMultiWithHaving +SELECT MIN(salary) min, MAX(salary) max, gender g, languages l, COUNT(*) c FROM "test_emp" WHERE languages > 0 GROUP BY g, languages HAVING max > 74600 ORDER BY gender, languages; + + // filter on count (which is a special agg) aggMultiWithHavingOnCount -SELECT MIN(salary) min, MAX(salary) max, gender g, COUNT(*) c FROM "test_emp" WHERE languages > 0 GROUP BY g HAVING c > 40 ORDER BY gender; \ No newline at end of file +SELECT MIN(salary) min, MAX(salary) max, gender g, COUNT(*) c FROM "test_emp" WHERE languages > 0 GROUP BY g HAVING c > 40 ORDER BY gender; +aggMultiGroupByMultiWithHavingOnCount +SELECT MIN(salary) min, MAX(salary) max, gender g, languages l, COUNT(*) c FROM "test_emp" WHERE languages > 0 GROUP BY g, languages HAVING c > 40 ORDER BY gender, languages; \ No newline at end of file diff --git a/x-pack/qa/sql/src/main/resources/docs.csv-spec b/x-pack/qa/sql/src/main/resources/docs.csv-spec index 8bf74bc4a2f..54509b21df3 100644 --- a/x-pack/qa/sql/src/main/resources/docs.csv-spec +++ b/x-pack/qa/sql/src/main/resources/docs.csv-spec @@ -456,6 +456,27 @@ SELECT languages + 1 AS l FROM emp GROUP BY l; // end::groupByExpression ; +groupByMulti +// tag::groupByMulti +SELECT gender g, languages l, COUNT(*) c FROM "emp" GROUP BY g, l ORDER BY languages ASC, gender DESC; + + g | l | c +---------------+---------------+--------------- +F |2 |4 +F |3 |8 +F |4 |7 +F |5 |7 +F |6 |11 +M |2 |12 +M |3 |12 +M |4 |15 +M |5 |11 +M |6 |13 + +// end::groupByMulti +; + + groupByAndAgg // tag::groupByAndAgg SELECT gender AS g, COUNT(*) AS c FROM emp GROUP BY gender;