diff --git a/jOOQ/src/main/java/org/jooq/Select.java b/jOOQ/src/main/java/org/jooq/Select.java index 220fdb1300..3f0e831862 100644 --- a/jOOQ/src/main/java/org/jooq/Select.java +++ b/jOOQ/src/main/java/org/jooq/Select.java @@ -192,6 +192,7 @@ public interface Select extends ResultQuery, TableLike, @NotNull Select $from(MList> from); @Nullable Condition $where(); @NotNull MList $groupBy(); + boolean $groupByDistinct(); @Nullable Condition $having(); @NotNull MList $window(); @Nullable Condition $qualify(); diff --git a/jOOQ/src/main/java/org/jooq/SelectGroupByStep.java b/jOOQ/src/main/java/org/jooq/SelectGroupByStep.java index 962c690287..17b346e55c 100644 --- a/jOOQ/src/main/java/org/jooq/SelectGroupByStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectGroupByStep.java @@ -37,8 +37,11 @@ */ package org.jooq; +import org.jooq.impl.DSL; + import org.jetbrains.annotations.*; +import static org.jooq.SQLDialect.POSTGRES; import java.util.Collection; @@ -123,4 +126,26 @@ public interface SelectGroupByStep extends SelectHavingStep @NotNull @CheckReturnValue @Support SelectHavingStep groupBy(Collection fields); + + /** + * Add a GROUP BY DISTINCT clause to the query + *

+ * This is mostly useful when combined with + * {@link DSL#groupingSets(Field[]...)} to remove duplicate grouping set + * results prior to aggregation and projection. + */ + @NotNull @CheckReturnValue + @Support({ POSTGRES }) + SelectHavingStep groupByDistinct(GroupField... fields); + + /** + * Add a GROUP BY DISTINCT clause to the query + *

+ * This is mostly useful when combined with + * {@link DSL#groupingSets(Field[]...)} to remove duplicate grouping set + * results prior to aggregation and projection. + */ + @NotNull @CheckReturnValue + @Support({ POSTGRES }) + SelectHavingStep groupByDistinct(Collection fields); } diff --git a/jOOQ/src/main/java/org/jooq/SelectQuery.java b/jOOQ/src/main/java/org/jooq/SelectQuery.java index bf55c1f062..51e5d25d87 100644 --- a/jOOQ/src/main/java/org/jooq/SelectQuery.java +++ b/jOOQ/src/main/java/org/jooq/SelectQuery.java @@ -71,6 +71,7 @@ import static org.jooq.SQLDialect.POSTGRES; // ... // ... // ... +// ... import static org.jooq.SQLDialect.SQLITE; // ... // ... @@ -341,6 +342,16 @@ public interface SelectQuery extends Select, ConditionProvi @Support void addGroupBy(Collection fields); + /** + * Specifies the GROUP BY DISTINCT clause. + *

+ * This is mostly useful when combined with + * {@link DSL#groupingSets(Field[]...)} to remove duplicate grouping set + * results prior to aggregation and projection. + */ + @Support({ POSTGRES }) + void setGroupByDistinct(boolean groupByDistinct); + /** * Adds a new condition to the having clause of the query, connecting it * with each other with {@link Operator#AND}. diff --git a/jOOQ/src/main/java/org/jooq/impl/ParameterImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParameterImpl.java index da23d81059..4d05a7a593 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParameterImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParameterImpl.java @@ -43,6 +43,7 @@ package org.jooq.impl; import static org.jooq.SQLDialect.POSTGRES; // ... // ... +// ... import static org.jooq.impl.Keywords.K_IN; import static org.jooq.impl.Keywords.K_INOUT; import static org.jooq.impl.Keywords.K_OUT; diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index c8e90e81c8..4bdf5e9935 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -1775,6 +1775,9 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { List groupBy; if (parseKeywordIf("GROUP BY")) { + if (!parseKeywordIf("ALL") && parseKeywordIf("DISTINCT")) + result.setGroupByDistinct(true); + if (parseIf('(')) { parse(')'); result.addGroupBy(); diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java index 73a798a6ce..4588e214d6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java @@ -702,6 +702,20 @@ implements return this; } + @Override + public final SelectImpl groupByDistinct(GroupField... fields) { + getQuery().addGroupBy(fields); + getQuery().setGroupByDistinct(true); + return this; + } + + @Override + public final SelectImpl groupByDistinct(Collection fields) { + getQuery().addGroupBy(fields); + getQuery().setGroupByDistinct(true); + return this; + } + @Override @@ -3395,6 +3409,11 @@ implements return getDelegate().$groupBy(); } + @Override + public final boolean $groupByDistinct() { + return getDelegate().$groupByDistinct(); + } + @Override public final Condition $having() { return getDelegate().$having(); diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 9783c86b97..5bccea389d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -389,6 +389,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp private final ConditionProviderImpl condition; private boolean grouping; private QueryPartList groupBy; + private boolean groupByDistinct; private final ConditionProviderImpl having; private WindowList window; private final ConditionProviderImpl qualify; @@ -524,6 +525,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp + private final SelectQueryImpl copyTo(CopyClause clause, boolean scalarSelect, SelectQueryImpl result) { @@ -552,6 +554,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp result.grouping = grouping; result.groupBy = groupBy; + result.groupByDistinct = groupByDistinct; result.having.setWhere(having.getWhere()); if (window != null) result.addWindow(window); @@ -2402,8 +2405,12 @@ final class SelectQueryImpl extends AbstractResultQuery imp if (grouping) { context.formatSeparator() - .visit(K_GROUP_BY) - .separatorRequired(true); + .visit(K_GROUP_BY); + + if (groupByDistinct) + context.sql(' ').visit(K_DISTINCT); + + context.separatorRequired(true); // [#1665] Empty GROUP BY () clauses need parentheses if (Tools.isEmpty(groupBy)) { @@ -3854,6 +3861,9 @@ final class SelectQueryImpl extends AbstractResultQuery imp final void setGrouping() { grouping = true; + + if (groupBy == null) + groupBy = new QueryPartList<>(); } final ConditionProviderImpl getWhere(Context ctx) { @@ -4149,13 +4159,15 @@ final class SelectQueryImpl extends AbstractResultQuery imp @Override public final void addGroupBy(Collection fields) { setGrouping(); - - if (groupBy == null) - groupBy = new QueryPartList<>(); - groupBy.addAll(fields); } + @Override + public final void setGroupByDistinct(boolean groupByDistinct) { + setGrouping(); + this.groupByDistinct = groupByDistinct; + } + @Override public final void addGroupBy(GroupField... fields) { addGroupBy(Arrays.asList(fields)); @@ -4543,6 +4555,11 @@ final class SelectQueryImpl extends AbstractResultQuery imp return groupBy == null ? QueryPartList.emptyList() : groupBy; } + @Override + public final boolean $groupByDistinct() { + return groupByDistinct; + } + @Override public final Condition $having() { return having; @@ -4594,16 +4611,18 @@ final class SelectQueryImpl extends AbstractResultQuery imp $from(), $where(), $groupBy(), + $groupByDistinct(), $having(), $window(), $qualify(), $orderBy(), - (cte, s, d, f, c, g, h, w, q, o) -> { + (cte, s, d, f, c, g, gd, h, w, q, o) -> { SelectQueryImpl r = new SelectQueryImpl<>(configuration(), cte, d); r.select.addAll(s); r.from.addAll(f); r.condition.addConditions(extractCondition(c)); r.groupBy = g; + r.groupByDistinct = gd; r.having.addConditions(extractCondition(h)); r.addWindow(w); r.qualify.addConditions(extractCondition(q));