Skip to content

Commit ef38a4d

Browse files
committed
Add GROUP BY ALL.
GROUP BY ALL is a form of GROUP BY that adds any TargetExpr that does not contain an aggregate or window function into the groupClause of the query, making it exactly equivalent to specifying those same expressions in an explicit GROUP BY list. This feature is useful for certain kinds of data exploration. It's already present in some other DBMSes, and the SQL committee recently accepted it into the standard, so we can be reasonably confident in the syntax being stable. We do have to invent part of the semantics, as the standard doesn't allow for expressions in GROUP BY, so they haven't specified what to do with window functions. We assume that those should be treated like aggregates, i.e., left out of the constructed GROUP BY list. In passing, wordsmith some existing documentation about GROUP BY, and update some neglected synopsis entries in select_into.sgml. Author: David Christensen <david@pgguru.net> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/CAHM0NXjz0kDwtzoe-fnHAqPB1qA8_VJN0XAmCgUZ+iPnvP5LbA@mail.gmail.com
1 parent b91067c commit ef38a4d

12 files changed

Lines changed: 312 additions & 15 deletions

File tree

doc/src/sgml/queries.sgml

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,10 +1145,36 @@ SELECT product_id, p.name, (sum(s.units) * p.price) AS sales
11451145

11461146
<para>
11471147
In strict SQL, <literal>GROUP BY</literal> can only group by columns of
1148-
the source table but <productname>PostgreSQL</productname> extends
1148+
the source table, but <productname>PostgreSQL</productname> extends
11491149
this to also allow <literal>GROUP BY</literal> to group by columns in the
11501150
select list. Grouping by value expressions instead of simple
1151-
column names is also allowed.
1151+
column names is also allowed (but <literal>GROUP BY</literal>
1152+
expressions cannot contain aggregate functions or window functions).
1153+
</para>
1154+
1155+
<para>
1156+
PostgreSQL also supports the syntax <literal>GROUP BY ALL</literal>,
1157+
which is equivalent to explicitly writing all select-list entries that
1158+
do not contain either an aggregate function or a window function.
1159+
This can greatly simplify ad-hoc exploration of data.
1160+
As an example, these queries are equivalent:
1161+
<screen>
1162+
<prompt>=&gt;</prompt> <userinput>SELECT a, b, a + b, sum(c) FROM test1 GROUP BY ALL;</userinput>
1163+
a | b | ?column? | sum
1164+
---+---+----------+----
1165+
1 | 4 | 5 | 9
1166+
2 | 5 | 7 | 12
1167+
3 | 6 | 9 | 15
1168+
(3 rows)
1169+
1170+
<prompt>=&gt;</prompt> <userinput>SELECT a, b, a + b, sum(c) FROM test1 GROUP BY a, b, a + b;</userinput>
1171+
a | b | ?column? | sum
1172+
---+---+----------+----
1173+
1 | 4 | 5 | 9
1174+
2 | 5 | 7 | 12
1175+
3 | 6 | 9 | 15
1176+
(3 rows)
1177+
</screen>
11521178
</para>
11531179

11541180
<indexterm>

doc/src/sgml/ref/select.sgml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
3737
[ { * | <replaceable class="parameter">expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] } [, ...] ]
3838
[ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
3939
[ WHERE <replaceable class="parameter">condition</replaceable> ]
40-
[ GROUP BY [ ALL | DISTINCT ] <replaceable class="parameter">grouping_element</replaceable> [, ...] ]
40+
[ GROUP BY { ALL | [ ALL | DISTINCT ] <replaceable class="parameter">grouping_element</replaceable> [, ...] } ]
4141
[ HAVING <replaceable class="parameter">condition</replaceable> ]
4242
[ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...] ]
4343
[ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] <replaceable class="parameter">select</replaceable> ]
@@ -796,7 +796,7 @@ WHERE <replaceable class="parameter">condition</replaceable>
796796
<para>
797797
The optional <literal>GROUP BY</literal> clause has the general form
798798
<synopsis>
799-
GROUP BY [ ALL | DISTINCT ] <replaceable class="parameter">grouping_element</replaceable> [, ...]
799+
GROUP BY { ALL | [ ALL | DISTINCT ] <replaceable class="parameter">grouping_element</replaceable> [, ...] }
800800
</synopsis>
801801
</para>
802802

@@ -808,21 +808,31 @@ GROUP BY [ ALL | DISTINCT ] <replaceable class="parameter">grouping_element</rep
808808
<replaceable class="parameter">grouping_element</replaceable>
809809
can be an input column name, or the name or ordinal number of an
810810
output column (<command>SELECT</command> list item), or an arbitrary
811-
expression formed from input-column values. In case of ambiguity,
811+
expression formed from input-column values; however, it cannot contain
812+
an aggregate function or a window function. In case of ambiguity,
812813
a <literal>GROUP BY</literal> name will be interpreted as an
813814
input-column name rather than an output column name.
814815
</para>
815816

817+
<para>
818+
The form <literal>GROUP BY ALL</literal> with no explicit
819+
<replaceable class="parameter">grouping_elements</replaceable>
820+
provided is equivalent to writing <literal>GROUP BY</literal> with the
821+
numbers of all <command>SELECT</command> output columns that do not
822+
contain either an aggregate function or a window function.
823+
</para>
824+
816825
<para>
817826
If any of <literal>GROUPING SETS</literal>, <literal>ROLLUP</literal> or
818827
<literal>CUBE</literal> are present as grouping elements, then the
819828
<literal>GROUP BY</literal> clause as a whole defines some number of
820829
independent <replaceable>grouping sets</replaceable>. The effect of this is
821830
equivalent to constructing a <literal>UNION ALL</literal> between
822-
subqueries with the individual grouping sets as their
831+
subqueries having the individual grouping sets as their
823832
<literal>GROUP BY</literal> clauses. The optional <literal>DISTINCT</literal>
824-
clause removes duplicate sets before processing; it does <emphasis>not</emphasis>
825-
transform the <literal>UNION ALL</literal> into a <literal>UNION DISTINCT</literal>.
833+
key word removes duplicate grouping sets before processing; it does <emphasis>not</emphasis>
834+
transform the implied <literal>UNION ALL</literal> into
835+
a <literal>UNION DISTINCT</literal>.
826836
For further details on the handling
827837
of grouping sets see <xref linkend="queries-grouping-sets"/>.
828838
</para>

doc/src/sgml/ref/select_into.sgml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
2727
INTO [ TEMPORARY | TEMP | UNLOGGED ] [ TABLE ] <replaceable class="parameter">new_table</replaceable>
2828
[ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
2929
[ WHERE <replaceable class="parameter">condition</replaceable> ]
30-
[ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...] ]
30+
[ GROUP BY { ALL | [ ALL | DISTINCT ] <replaceable class="parameter">grouping_element</replaceable> [, ...] } ]
3131
[ HAVING <replaceable class="parameter">condition</replaceable> ]
3232
[ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...] ]
3333
[ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] <replaceable class="parameter">select</replaceable> ]
3434
[ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
3535
[ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
3636
[ OFFSET <replaceable class="parameter">start</replaceable> [ ROW | ROWS ] ]
37-
[ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } ONLY ]
38-
[ FOR { UPDATE | SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ]
37+
[ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } { ONLY | WITH TIES } ]
38+
[ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF <replaceable class="parameter">from_reference</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]
3939
</synopsis>
4040
</refsynopsisdiv>
4141

src/backend/parser/analyze.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,12 +1467,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt,
14671467

14681468
qry->groupClause = transformGroupClause(pstate,
14691469
stmt->groupClause,
1470+
stmt->groupByAll,
14701471
&qry->groupingSets,
14711472
&qry->targetList,
14721473
qry->sortClause,
14731474
EXPR_KIND_GROUP_BY,
14741475
false /* allow SQL92 rules */ );
14751476
qry->groupDistinct = stmt->groupDistinct;
1477+
qry->groupByAll = stmt->groupByAll;
14761478

14771479
if (stmt->distinctClause == NIL)
14781480
{

src/backend/parser/gram.y

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ typedef struct SelectLimit
120120
typedef struct GroupClause
121121
{
122122
bool distinct;
123+
bool all;
123124
List *list;
124125
} GroupClause;
125126

@@ -12993,6 +12994,7 @@ simple_select:
1299312994
n->whereClause = $6;
1299412995
n->groupClause = ($7)->list;
1299512996
n->groupDistinct = ($7)->distinct;
12997+
n->groupByAll = ($7)->all;
1299612998
n->havingClause = $8;
1299712999
n->windowClause = $9;
1299813000
$$ = (Node *) n;
@@ -13010,6 +13012,7 @@ simple_select:
1301013012
n->whereClause = $6;
1301113013
n->groupClause = ($7)->list;
1301213014
n->groupDistinct = ($7)->distinct;
13015+
n->groupByAll = ($7)->all;
1301313016
n->havingClause = $8;
1301413017
n->windowClause = $9;
1301513018
$$ = (Node *) n;
@@ -13507,14 +13510,24 @@ group_clause:
1350713510
GroupClause *n = (GroupClause *) palloc(sizeof(GroupClause));
1350813511

1350913512
n->distinct = $3 == SET_QUANTIFIER_DISTINCT;
13513+
n->all = false;
1351013514
n->list = $4;
1351113515
$$ = n;
1351213516
}
13517+
| GROUP_P BY ALL
13518+
{
13519+
GroupClause *n = (GroupClause *) palloc(sizeof(GroupClause));
13520+
n->distinct = false;
13521+
n->all = true;
13522+
n->list = NIL;
13523+
$$ = n;
13524+
}
1351313525
| /*EMPTY*/
1351413526
{
1351513527
GroupClause *n = (GroupClause *) palloc(sizeof(GroupClause));
1351613528

1351713529
n->distinct = false;
13530+
n->all = false;
1351813531
n->list = NIL;
1351913532
$$ = n;
1352013533
}
@@ -17618,6 +17631,7 @@ PLpgSQL_Expr: opt_distinct_clause opt_target_list
1761817631
n->whereClause = $4;
1761917632
n->groupClause = ($5)->list;
1762017633
n->groupDistinct = ($5)->distinct;
17634+
n->groupByAll = ($5)->all;
1762117635
n->havingClause = $6;
1762217636
n->windowClause = $7;
1762317637
n->sortClause = $8;

src/backend/parser/parse_clause.c

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2598,6 +2598,9 @@ transformGroupingSet(List **flatresult,
25982598
* GROUP BY items will be added to the targetlist (as resjunk columns)
25992599
* if not already present, so the targetlist must be passed by reference.
26002600
*
2601+
* If GROUP BY ALL is specified, the groupClause will be inferred to be all
2602+
* non-aggregate, non-window expressions in the targetlist.
2603+
*
26012604
* This is also used for window PARTITION BY clauses (which act almost the
26022605
* same, but are always interpreted per SQL99 rules).
26032606
*
@@ -2622,14 +2625,16 @@ transformGroupingSet(List **flatresult,
26222625
*
26232626
* pstate ParseState
26242627
* grouplist clause to transform
2628+
* groupByAll is this a GROUP BY ALL statement?
26252629
* groupingSets reference to list to contain the grouping set tree
26262630
* targetlist reference to TargetEntry list
26272631
* sortClause ORDER BY clause (SortGroupClause nodes)
26282632
* exprKind expression kind
26292633
* useSQL99 SQL99 rather than SQL92 syntax
26302634
*/
26312635
List *
2632-
transformGroupClause(ParseState *pstate, List *grouplist, List **groupingSets,
2636+
transformGroupClause(ParseState *pstate, List *grouplist, bool groupByAll,
2637+
List **groupingSets,
26332638
List **targetlist, List *sortClause,
26342639
ParseExprKind exprKind, bool useSQL99)
26352640
{
@@ -2640,6 +2645,63 @@ transformGroupClause(ParseState *pstate, List *grouplist, List **groupingSets,
26402645
bool hasGroupingSets = false;
26412646
Bitmapset *seen_local = NULL;
26422647

2648+
/* Handle GROUP BY ALL */
2649+
if (groupByAll)
2650+
{
2651+
/* There cannot have been any explicit grouplist items */
2652+
Assert(grouplist == NIL);
2653+
2654+
/* Iterate over targets, adding acceptable ones to the result list */
2655+
foreach_ptr(TargetEntry, tle, *targetlist)
2656+
{
2657+
/* Ignore junk TLEs */
2658+
if (tle->resjunk)
2659+
continue;
2660+
2661+
/*
2662+
* TLEs containing aggregates are not okay to add to GROUP BY
2663+
* (compare checkTargetlistEntrySQL92). But the SQL standard
2664+
* directs us to skip them, so it's fine.
2665+
*/
2666+
if (pstate->p_hasAggs &&
2667+
contain_aggs_of_level((Node *) tle->expr, 0))
2668+
continue;
2669+
2670+
/*
2671+
* Likewise, TLEs containing window functions are not okay to add
2672+
* to GROUP BY. At this writing, the SQL standard is silent on
2673+
* what to do with them, but by analogy to aggregates we'll just
2674+
* skip them.
2675+
*/
2676+
if (pstate->p_hasWindowFuncs &&
2677+
contain_windowfuncs((Node *) tle->expr))
2678+
continue;
2679+
2680+
/*
2681+
* Otherwise, add the TLE to the result using default sort/group
2682+
* semantics. We specify the parse location as the TLE's
2683+
* location, despite the comment for addTargetToGroupList
2684+
* discouraging that. The only other thing we could point to is
2685+
* the ALL keyword, which seems unhelpful when there are multiple
2686+
* TLEs.
2687+
*/
2688+
result = addTargetToGroupList(pstate, tle,
2689+
result, *targetlist,
2690+
exprLocation((Node *) tle->expr));
2691+
}
2692+
2693+
/* If we found any acceptable targets, we're done */
2694+
if (result != NIL)
2695+
return result;
2696+
2697+
/*
2698+
* Otherwise, the SQL standard says to treat it like "GROUP BY ()".
2699+
* Build a representation of that, and let the rest of this function
2700+
* handle it.
2701+
*/
2702+
grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY, NIL, -1));
2703+
}
2704+
26432705
/*
26442706
* Recursively flatten implicit RowExprs. (Technically this is only needed
26452707
* for GROUP BY, per the syntax rules for grouping sets, but we do it
@@ -2818,6 +2880,7 @@ transformWindowDefinitions(ParseState *pstate,
28182880
true /* force SQL99 rules */ );
28192881
partitionClause = transformGroupClause(pstate,
28202882
windef->partitionClause,
2883+
false /* not GROUP BY ALL */ ,
28212884
NULL,
28222885
targetlist,
28232886
orderClause,

src/backend/utils/adt/ruleutils.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6186,7 +6186,9 @@ get_basic_select_query(Query *query, deparse_context *context)
61866186
save_ingroupby = context->inGroupBy;
61876187
context->inGroupBy = true;
61886188

6189-
if (query->groupingSets == NIL)
6189+
if (query->groupByAll)
6190+
appendStringInfoString(buf, "ALL");
6191+
else if (query->groupingSets == NIL)
61906192
{
61916193
sep = "";
61926194
foreach(l, query->groupClause)

src/include/catalog/catversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
*/
5858

5959
/* yyyymmddN */
60-
#define CATALOG_VERSION_NO 202509191
60+
#define CATALOG_VERSION_NO 202509291
6161

6262
#endif

src/include/nodes/parsenodes.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ typedef struct Query
214214
List *returningList; /* return-values list (of TargetEntry) */
215215

216216
List *groupClause; /* a list of SortGroupClause's */
217-
bool groupDistinct; /* is the group by clause distinct? */
217+
bool groupDistinct; /* was GROUP BY DISTINCT used? */
218+
bool groupByAll; /* was GROUP BY ALL used? */
218219

219220
List *groupingSets; /* a list of GroupingSet's if present */
220221

@@ -2192,6 +2193,7 @@ typedef struct SelectStmt
21922193
Node *whereClause; /* WHERE qualification */
21932194
List *groupClause; /* GROUP BY clauses */
21942195
bool groupDistinct; /* Is this GROUP BY DISTINCT? */
2196+
bool groupByAll; /* Is this GROUP BY ALL? */
21952197
Node *havingClause; /* HAVING conditional-expression */
21962198
List *windowClause; /* WINDOW window_name AS (...), ... */
21972199

src/include/parser/parse_clause.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern Node *transformLimitClause(ParseState *pstate, Node *clause,
2626
ParseExprKind exprKind, const char *constructName,
2727
LimitOption limitOption);
2828
extern List *transformGroupClause(ParseState *pstate, List *grouplist,
29+
bool groupByAll,
2930
List **groupingSets,
3031
List **targetlist, List *sortClause,
3132
ParseExprKind exprKind, bool useSQL99);

0 commit comments

Comments
 (0)