mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different GROUP BY clauses at once. Each grouping set returns rows with columns grouped by in other sets set to NULL. This could previously be achieved by doing each grouping as a separate query, conjoined by UNION ALLs. Besides being considerably more concise, grouping sets will in many cases be faster, requiring only one scan over the underlying data. The current implementation of grouping sets only supports using sorting for input. Individual sets that share a sort order are computed in one pass. If there are sets that don't share a sort order, additional sort & aggregation steps are performed. These additional passes are sourced by the previous sort step; thus avoiding repeated scans of the source data. The code is structured in a way that adding support for purely using hash aggregation or a mix of hashing and sorting is possible. Sorting was chosen to be supported first, as it is the most generic method of implementation. Instead of, as in an earlier versions of the patch, representing the chain of sort and aggregation steps as full blown planner and executor nodes, all but the first sort are performed inside the aggregation node itself. This avoids the need to do some unusual gymnastics to handle having to return aggregated and non-aggregated tuples from underlying nodes, as well as having to shut down underlying nodes early to limit memory usage. The optimizer still builds Sort/Agg node to describe each phase, but they're not part of the plan tree, but instead additional data for the aggregation node. They're a convenient and preexisting way to describe aggregation and sorting. The first (and possibly only) sort step is still performed as a separate execution step. That retains similarity with existing group by plans, makes rescans fairly simple, avoids very deep plans (leading to slow explains) and easily allows to avoid the sorting step if the underlying data is sorted by other means. A somewhat ugly side of this patch is having to deal with a grammar ambiguity between the new CUBE keyword and the cube extension/functions named cube (and rollup). To avoid breaking existing deployments of the cube extension it has not been renamed, neither has cube been made a reserved keyword. Instead precedence hacking is used to make GROUP BY cube(..) refer to the CUBE grouping sets feature, and not the function cube(). To actually group by a function cube(), unlikely as that might be, the function name has to be quoted. Needs a catversion bump because stored rules may change. Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
This commit is contained in:
@ -839,6 +839,8 @@ _copyAgg(const Agg *from)
|
||||
COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
|
||||
}
|
||||
COPY_SCALAR_FIELD(numGroups);
|
||||
COPY_NODE_FIELD(groupingSets);
|
||||
COPY_NODE_FIELD(chain);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
@ -1208,6 +1210,23 @@ _copyAggref(const Aggref *from)
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* _copyGroupingFunc
|
||||
*/
|
||||
static GroupingFunc *
|
||||
_copyGroupingFunc(const GroupingFunc *from)
|
||||
{
|
||||
GroupingFunc *newnode = makeNode(GroupingFunc);
|
||||
|
||||
COPY_NODE_FIELD(args);
|
||||
COPY_NODE_FIELD(refs);
|
||||
COPY_NODE_FIELD(cols);
|
||||
COPY_SCALAR_FIELD(agglevelsup);
|
||||
COPY_LOCATION_FIELD(location);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* _copyWindowFunc
|
||||
*/
|
||||
@ -2152,6 +2171,18 @@ _copySortGroupClause(const SortGroupClause *from)
|
||||
return newnode;
|
||||
}
|
||||
|
||||
static GroupingSet *
|
||||
_copyGroupingSet(const GroupingSet *from)
|
||||
{
|
||||
GroupingSet *newnode = makeNode(GroupingSet);
|
||||
|
||||
COPY_SCALAR_FIELD(kind);
|
||||
COPY_NODE_FIELD(content);
|
||||
COPY_LOCATION_FIELD(location);
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
static WindowClause *
|
||||
_copyWindowClause(const WindowClause *from)
|
||||
{
|
||||
@ -2676,6 +2707,7 @@ _copyQuery(const Query *from)
|
||||
COPY_NODE_FIELD(onConflict);
|
||||
COPY_NODE_FIELD(returningList);
|
||||
COPY_NODE_FIELD(groupClause);
|
||||
COPY_NODE_FIELD(groupingSets);
|
||||
COPY_NODE_FIELD(havingQual);
|
||||
COPY_NODE_FIELD(windowClause);
|
||||
COPY_NODE_FIELD(distinctClause);
|
||||
@ -4309,6 +4341,9 @@ copyObject(const void *from)
|
||||
case T_Aggref:
|
||||
retval = _copyAggref(from);
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
retval = _copyGroupingFunc(from);
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
retval = _copyWindowFunc(from);
|
||||
break;
|
||||
@ -4878,6 +4913,9 @@ copyObject(const void *from)
|
||||
case T_SortGroupClause:
|
||||
retval = _copySortGroupClause(from);
|
||||
break;
|
||||
case T_GroupingSet:
|
||||
retval = _copyGroupingSet(from);
|
||||
break;
|
||||
case T_WindowClause:
|
||||
retval = _copyWindowClause(from);
|
||||
break;
|
||||
|
@ -207,6 +207,21 @@ _equalAggref(const Aggref *a, const Aggref *b)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalGroupingFunc(const GroupingFunc *a, const GroupingFunc *b)
|
||||
{
|
||||
COMPARE_NODE_FIELD(args);
|
||||
|
||||
/*
|
||||
* We must not compare the refs or cols field
|
||||
*/
|
||||
|
||||
COMPARE_SCALAR_FIELD(agglevelsup);
|
||||
COMPARE_LOCATION_FIELD(location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
|
||||
{
|
||||
@ -896,6 +911,7 @@ _equalQuery(const Query *a, const Query *b)
|
||||
COMPARE_NODE_FIELD(onConflict);
|
||||
COMPARE_NODE_FIELD(returningList);
|
||||
COMPARE_NODE_FIELD(groupClause);
|
||||
COMPARE_NODE_FIELD(groupingSets);
|
||||
COMPARE_NODE_FIELD(havingQual);
|
||||
COMPARE_NODE_FIELD(windowClause);
|
||||
COMPARE_NODE_FIELD(distinctClause);
|
||||
@ -2426,6 +2442,16 @@ _equalSortGroupClause(const SortGroupClause *a, const SortGroupClause *b)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalGroupingSet(const GroupingSet *a, const GroupingSet *b)
|
||||
{
|
||||
COMPARE_SCALAR_FIELD(kind);
|
||||
COMPARE_NODE_FIELD(content);
|
||||
COMPARE_LOCATION_FIELD(location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
_equalWindowClause(const WindowClause *a, const WindowClause *b)
|
||||
{
|
||||
@ -2693,6 +2719,9 @@ equal(const void *a, const void *b)
|
||||
case T_Aggref:
|
||||
retval = _equalAggref(a, b);
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
retval = _equalGroupingFunc(a, b);
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
retval = _equalWindowFunc(a, b);
|
||||
break;
|
||||
@ -3249,6 +3278,9 @@ equal(const void *a, const void *b)
|
||||
case T_SortGroupClause:
|
||||
retval = _equalSortGroupClause(a, b);
|
||||
break;
|
||||
case T_GroupingSet:
|
||||
retval = _equalGroupingSet(a, b);
|
||||
break;
|
||||
case T_WindowClause:
|
||||
retval = _equalWindowClause(a, b);
|
||||
break;
|
||||
|
@ -822,6 +822,32 @@ list_intersection(const List *list1, const List *list2)
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* As list_intersection but operates on lists of integers.
|
||||
*/
|
||||
List *
|
||||
list_intersection_int(const List *list1, const List *list2)
|
||||
{
|
||||
List *result;
|
||||
const ListCell *cell;
|
||||
|
||||
if (list1 == NIL || list2 == NIL)
|
||||
return NIL;
|
||||
|
||||
Assert(IsIntegerList(list1));
|
||||
Assert(IsIntegerList(list2));
|
||||
|
||||
result = NIL;
|
||||
foreach(cell, list1)
|
||||
{
|
||||
if (list_member_int(list2, lfirst_int(cell)))
|
||||
result = lappend_int(result, lfirst_int(cell));
|
||||
}
|
||||
|
||||
check_list_invariants(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a list that contains all the cells in list1 that are not in
|
||||
* list2. The returned list is freshly allocated via palloc(), but the
|
||||
|
@ -554,3 +554,18 @@ makeFuncCall(List *name, List *args, int location)
|
||||
n->location = location;
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* makeGroupingSet
|
||||
*
|
||||
*/
|
||||
GroupingSet *
|
||||
makeGroupingSet(GroupingSetKind kind, List *content, int location)
|
||||
{
|
||||
GroupingSet *n = makeNode(GroupingSet);
|
||||
|
||||
n->kind = kind;
|
||||
n->content = content;
|
||||
n->location = location;
|
||||
return n;
|
||||
}
|
||||
|
@ -54,6 +54,9 @@ exprType(const Node *expr)
|
||||
case T_Aggref:
|
||||
type = ((const Aggref *) expr)->aggtype;
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
type = INT4OID;
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
type = ((const WindowFunc *) expr)->wintype;
|
||||
break;
|
||||
@ -750,6 +753,9 @@ exprCollation(const Node *expr)
|
||||
case T_Aggref:
|
||||
coll = ((const Aggref *) expr)->aggcollid;
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
coll = InvalidOid;
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
coll = ((const WindowFunc *) expr)->wincollid;
|
||||
break;
|
||||
@ -986,6 +992,9 @@ exprSetCollation(Node *expr, Oid collation)
|
||||
case T_Aggref:
|
||||
((Aggref *) expr)->aggcollid = collation;
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
Assert(!OidIsValid(collation));
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
((WindowFunc *) expr)->wincollid = collation;
|
||||
break;
|
||||
@ -1202,6 +1211,9 @@ exprLocation(const Node *expr)
|
||||
/* function name should always be the first thing */
|
||||
loc = ((const Aggref *) expr)->location;
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
loc = ((const GroupingFunc *) expr)->location;
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
/* function name should always be the first thing */
|
||||
loc = ((const WindowFunc *) expr)->location;
|
||||
@ -1491,6 +1503,9 @@ exprLocation(const Node *expr)
|
||||
/* XMLSERIALIZE keyword should always be the first thing */
|
||||
loc = ((const XmlSerialize *) expr)->location;
|
||||
break;
|
||||
case T_GroupingSet:
|
||||
loc = ((const GroupingSet *) expr)->location;
|
||||
break;
|
||||
case T_WithClause:
|
||||
loc = ((const WithClause *) expr)->location;
|
||||
break;
|
||||
@ -1685,6 +1700,15 @@ expression_tree_walker(Node *node,
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
{
|
||||
GroupingFunc *grouping = (GroupingFunc *) node;
|
||||
|
||||
if (expression_tree_walker((Node *) grouping->args,
|
||||
walker, context))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
{
|
||||
WindowFunc *expr = (WindowFunc *) node;
|
||||
@ -2243,6 +2267,29 @@ expression_tree_mutator(Node *node,
|
||||
return (Node *) newnode;
|
||||
}
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
{
|
||||
GroupingFunc *grouping = (GroupingFunc *) node;
|
||||
GroupingFunc *newnode;
|
||||
|
||||
FLATCOPY(newnode, grouping, GroupingFunc);
|
||||
MUTATE(newnode->args, grouping->args, List *);
|
||||
|
||||
/*
|
||||
* We assume here that mutating the arguments does not change
|
||||
* the semantics, i.e. that the arguments are not mutated in a
|
||||
* way that makes them semantically different from their
|
||||
* previously matching expressions in the GROUP BY clause.
|
||||
*
|
||||
* If a mutator somehow wanted to do this, it would have to
|
||||
* handle the refs and cols lists itself as appropriate.
|
||||
*/
|
||||
newnode->refs = list_copy(grouping->refs);
|
||||
newnode->cols = list_copy(grouping->cols);
|
||||
|
||||
return (Node *) newnode;
|
||||
}
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
{
|
||||
WindowFunc *wfunc = (WindowFunc *) node;
|
||||
@ -2962,6 +3009,8 @@ raw_expression_tree_walker(Node *node,
|
||||
break;
|
||||
case T_RangeVar:
|
||||
return walker(((RangeVar *) node)->alias, context);
|
||||
case T_GroupingFunc:
|
||||
return walker(((GroupingFunc *) node)->args, context);
|
||||
case T_SubLink:
|
||||
{
|
||||
SubLink *sublink = (SubLink *) node;
|
||||
@ -3287,6 +3336,8 @@ raw_expression_tree_walker(Node *node,
|
||||
/* for now, constraints are ignored */
|
||||
}
|
||||
break;
|
||||
case T_GroupingSet:
|
||||
return walker(((GroupingSet *) node)->content, context);
|
||||
case T_LockingClause:
|
||||
return walker(((LockingClause *) node)->lockedRels, context);
|
||||
case T_XmlSerialize:
|
||||
|
@ -679,6 +679,9 @@ _outAgg(StringInfo str, const Agg *node)
|
||||
appendStringInfo(str, " %u", node->grpOperators[i]);
|
||||
|
||||
WRITE_LONG_FIELD(numGroups);
|
||||
|
||||
WRITE_NODE_FIELD(groupingSets);
|
||||
WRITE_NODE_FIELD(chain);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1003,6 +1006,18 @@ _outAggref(StringInfo str, const Aggref *node)
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
static void
|
||||
_outGroupingFunc(StringInfo str, const GroupingFunc *node)
|
||||
{
|
||||
WRITE_NODE_TYPE("GROUPINGFUNC");
|
||||
|
||||
WRITE_NODE_FIELD(args);
|
||||
WRITE_NODE_FIELD(refs);
|
||||
WRITE_NODE_FIELD(cols);
|
||||
WRITE_INT_FIELD(agglevelsup);
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
static void
|
||||
_outWindowFunc(StringInfo str, const WindowFunc *node)
|
||||
{
|
||||
@ -2364,6 +2379,7 @@ _outQuery(StringInfo str, const Query *node)
|
||||
WRITE_NODE_FIELD(onConflict);
|
||||
WRITE_NODE_FIELD(returningList);
|
||||
WRITE_NODE_FIELD(groupClause);
|
||||
WRITE_NODE_FIELD(groupingSets);
|
||||
WRITE_NODE_FIELD(havingQual);
|
||||
WRITE_NODE_FIELD(windowClause);
|
||||
WRITE_NODE_FIELD(distinctClause);
|
||||
@ -2398,6 +2414,16 @@ _outSortGroupClause(StringInfo str, const SortGroupClause *node)
|
||||
WRITE_BOOL_FIELD(hashable);
|
||||
}
|
||||
|
||||
static void
|
||||
_outGroupingSet(StringInfo str, const GroupingSet *node)
|
||||
{
|
||||
WRITE_NODE_TYPE("GROUPINGSET");
|
||||
|
||||
WRITE_ENUM_FIELD(kind, GroupingSetKind);
|
||||
WRITE_NODE_FIELD(content);
|
||||
WRITE_LOCATION_FIELD(location);
|
||||
}
|
||||
|
||||
static void
|
||||
_outWindowClause(StringInfo str, const WindowClause *node)
|
||||
{
|
||||
@ -3087,6 +3113,9 @@ _outNode(StringInfo str, const void *obj)
|
||||
case T_Aggref:
|
||||
_outAggref(str, obj);
|
||||
break;
|
||||
case T_GroupingFunc:
|
||||
_outGroupingFunc(str, obj);
|
||||
break;
|
||||
case T_WindowFunc:
|
||||
_outWindowFunc(str, obj);
|
||||
break;
|
||||
@ -3349,6 +3378,9 @@ _outNode(StringInfo str, const void *obj)
|
||||
case T_SortGroupClause:
|
||||
_outSortGroupClause(str, obj);
|
||||
break;
|
||||
case T_GroupingSet:
|
||||
_outGroupingSet(str, obj);
|
||||
break;
|
||||
case T_WindowClause:
|
||||
_outWindowClause(str, obj);
|
||||
break;
|
||||
|
@ -217,6 +217,7 @@ _readQuery(void)
|
||||
READ_NODE_FIELD(onConflict);
|
||||
READ_NODE_FIELD(returningList);
|
||||
READ_NODE_FIELD(groupClause);
|
||||
READ_NODE_FIELD(groupingSets);
|
||||
READ_NODE_FIELD(havingQual);
|
||||
READ_NODE_FIELD(windowClause);
|
||||
READ_NODE_FIELD(distinctClause);
|
||||
@ -292,6 +293,21 @@ _readSortGroupClause(void)
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readGroupingSet
|
||||
*/
|
||||
static GroupingSet *
|
||||
_readGroupingSet(void)
|
||||
{
|
||||
READ_LOCALS(GroupingSet);
|
||||
|
||||
READ_ENUM_FIELD(kind, GroupingSetKind);
|
||||
READ_NODE_FIELD(content);
|
||||
READ_LOCATION_FIELD(location);
|
||||
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readWindowClause
|
||||
*/
|
||||
@ -551,6 +567,23 @@ _readAggref(void)
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readGroupingFunc
|
||||
*/
|
||||
static GroupingFunc *
|
||||
_readGroupingFunc(void)
|
||||
{
|
||||
READ_LOCALS(GroupingFunc);
|
||||
|
||||
READ_NODE_FIELD(args);
|
||||
READ_NODE_FIELD(refs);
|
||||
READ_NODE_FIELD(cols);
|
||||
READ_INT_FIELD(agglevelsup);
|
||||
READ_LOCATION_FIELD(location);
|
||||
|
||||
READ_DONE();
|
||||
}
|
||||
|
||||
/*
|
||||
* _readWindowFunc
|
||||
*/
|
||||
@ -1386,6 +1419,8 @@ parseNodeString(void)
|
||||
return_value = _readWithCheckOption();
|
||||
else if (MATCH("SORTGROUPCLAUSE", 15))
|
||||
return_value = _readSortGroupClause();
|
||||
else if (MATCH("GROUPINGSET", 11))
|
||||
return_value = _readGroupingSet();
|
||||
else if (MATCH("WINDOWCLAUSE", 12))
|
||||
return_value = _readWindowClause();
|
||||
else if (MATCH("ROWMARKCLAUSE", 13))
|
||||
@ -1412,6 +1447,8 @@ parseNodeString(void)
|
||||
return_value = _readParam();
|
||||
else if (MATCH("AGGREF", 6))
|
||||
return_value = _readAggref();
|
||||
else if (MATCH("GROUPINGFUNC", 12))
|
||||
return_value = _readGroupingFunc();
|
||||
else if (MATCH("WINDOWFUNC", 10))
|
||||
return_value = _readWindowFunc();
|
||||
else if (MATCH("ARRAYREF", 8))
|
||||
|
Reference in New Issue
Block a user