diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4dd9d029e6..d61769da75 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14721,8 +14721,8 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
partition through the last peer of the current row. This is
likely to give unhelpful results for last_value and
sometimes also nth_value. You can redefine the frame by
- adding a suitable frame specification (RANGE or
- ROWS) to the OVER clause.
+ adding a suitable frame specification (RANGE,
+ ROWS or GROUPS) to the OVER clause.
See for more information
about frame specifications.
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 40c146ca40..26ff2f59fa 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -859,8 +859,8 @@ WINDOW window_name AS ( frame_clause can be one of
-{ RANGE | ROWS } frame_start [ frame_exclusion_clause ]
-{ RANGE | ROWS } BETWEEN frame_start AND frame_end [ frame_exclusion_clause ]
+{ RANGE | ROWS | GROUPS } frame_start [ frame_exclusion_clause ]
+{ RANGE | ROWS | GROUPS } BETWEEN frame_start AND frame_end [ frame_exclusion_clause ]
where frame_start and frame_end can be
@@ -879,6 +879,7 @@ UNBOUNDED FOLLOWING
EXCLUDE CURRENT ROW
+EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS
@@ -903,19 +904,20 @@ EXCLUDE NO OTHERS
In general, UNBOUNDED PRECEDING means that the frame
starts with the first row of the partition, and similarly
UNBOUNDED FOLLOWING means that the frame ends with the last
- row of the partition (regardless of RANGE or ROWS
- mode). In ROWS mode, CURRENT ROW
+ row of the partition (regardless of RANGE, ROWS
+ or GROUPS mode). In ROWS mode, CURRENT ROW
means that the frame starts or ends with the current row; but in
- RANGE mode it means that the frame starts or ends with
+ RANGE or GROUPS mode it means that the frame starts or ends with
the current row's first or last peer in the ORDER BY ordering.
The value PRECEDING and
value FOLLOWING cases differ depending on
- whether the frame clause is in ROWS or RANGE mode. In
+ whether the frame clause is in ROWS, RANGEor GROUPS mode. In
ROWS mode, they indicate that the frame starts or ends with the row that
many rows before or after the current row. In RANGE mode, they indicate that
the frame starts or ends when the ORDER BY column's value for each row is within the bounds
- specified by value for both the start and the end of the frame.
- In ROWS mode, value must be an integer expression not
+ specified by value for both the start and the end of the frame. In GROUPS mode,
+ they indicate the number of changes to the value of the ORDER BY columns (i.e., groups of peers).
+ In ROWS or GROUPS mode, value must be an integer expression not
containing any variables, aggregate functions, or window functions.In RANGE mode,
there must be exactly one ORDER BY column and if the column is an integer column,
then value must be an integer. If it is a date/time column, then
@@ -926,14 +928,15 @@ EXCLUDE NO OTHERS
For the frame_exclusion_clause, EXCLUDE CURRENT ROW
excludes the current row from the frame. EXCLUDE TIES excludes any peers of the current row from the
- frame. EXCLUDE NO OTHERS does nothing, but is provided in order to optionally document the intention
+ frame. EXCLUDE GROUP excludes both the current row and any peers of the current row from the frame.
+ EXCLUDE NO OTHERS does nothing, but is provided in order to optionally document the intention
not to exclude any other rows.
Beware that the ROWS options can produce unpredictable
results if the ORDER BY ordering does not order the rows
- uniquely. The RANGE options are designed to ensure that
+ uniquely. The RANGE and GROUPS options are designed to ensure that
rows that are peers in the ORDER BY ordering are treated
alike; all peer rows will be in the same frame.
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index f711c99987..87905ef2e2 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1805,8 +1805,8 @@ FROM generate_series(1,10) AS s(i);
and the optional frame_clause
can be one of
-{ RANGE | ROWS } frame_start [ frame_exclusion_clause ]
-{ RANGE | ROWS } BETWEEN frame_start AND frame_end [ frame_exclusion_clause ]
+{ RANGE | ROWS | GROUPS } frame_start [ frame_exclusion_clause ]
+{ RANGE | ROWS | GROUPS } BETWEEN frame_start AND frame_end [ frame_exclusion_clause ]
where frame_start and frame_end can be
one of
@@ -1820,6 +1820,7 @@ UNBOUNDED FOLLOWING
where the optional frame_exclusion_clause can be one of
EXCLUDE CURRENT ROW
+EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS
@@ -1863,8 +1864,8 @@ EXCLUDE NO OTHERS
the set of rows constituting the window frame, which is a
subset of the current partition, for those window functions that act on
the frame instead of the whole partition. The frame can be specified in
- either RANGE or ROWS mode; in either case, it
- runs from the frame_start to the
+ either RANGE, ROWS or GROUPS mode;
+ in each case, it runs from the frame_start to the
frame_end. If frame_end is omitted,
it defaults to CURRENT ROW.
@@ -1877,7 +1878,7 @@ EXCLUDE NO OTHERS
- In RANGE mode, a frame_start of
+ In RANGE or GROUPS mode, a frame_start of
CURRENT ROW means the frame starts with the current row's
first peer row (a row that ORDER BY considers
equivalent to the current row), while a frame_end of
@@ -1903,10 +1904,19 @@ EXCLUDE NO OTHERS
+ In GROUPS mode, value PRECEDING and
+ value FOLLOWING cases indicate that the frame starts or ends the specified
+ number of window framing groups before or after the current window framing group.
+ Two rows are in the same window framing group if they are peers, (i.e., their ORDER BY column values
+ match). This mode allows the selection of a frame by the number of changes to the ORDER BY columns.
+
+
+
For the frame_exclusion_clause, EXCLUDE CURRENT ROW
excludes the current row from the frame. EXCLUDE TIES excludes any peers of the current row from the
- frame. EXCLUDE NO OTHERS does nothing, but is provided in order to optionally document the intention
- not to exclude any other rows.
+ frame. EXCLUDE GROUP excludes both the current row and any peers of the current row from the frame.
+ EXCLUDE NO OTHERS does nothing, but is provided in order to optionally document the intention not to
+ exclude any other rows.
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 8e746f36d4..20d61f3780 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -498,7 +498,7 @@ T616 Null treatment option for LEAD and LAG functions NO
T617 FIRST_VALUE and LAST_VALUE function YES
T618 NTH_VALUE function NO function exists, but some options missing
T619 Nested window functions NO
-T620 WINDOW clause: GROUPS option NO
+T620 WINDOW clause: GROUPS option YES
T621 Enhanced numeric functions YES
T631 IN predicate with one list element YES
T641 Multiple column assignment NO only some syntax variants supported
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 4c6199fbe4..4e5b98f847 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -68,6 +68,8 @@ typedef struct WindowObjectData
int readptr; /* tuplestore read pointer for this fn */
int64 markpos; /* row that markptr is positioned on */
int64 seekpos; /* row that readptr is positioned on */
+ int64 winGroupsCount; /* number of window groups, used for GROUPS with values */
+ int64 *winGroupLen; /* length of each window group */
} WindowObjectData;
/*
@@ -181,6 +183,8 @@ static void begin_partition(WindowAggState *winstate);
static void spool_tuples(WindowAggState *winstate, int64 pos);
static void release_partition(WindowAggState *winstate);
+static bool row_is_in_group(WindowObject winobj, int64 currpos, int64 slotpos,
+ int64 offset, bool preceding, bool end);
static bool row_is_in_range(Oid sortColOid, Datum currval,
Datum slotval, Datum offset, bool preceding, bool end);
static int row_is_in_frame(WindowAggState *winstate, int64 pos,
@@ -686,9 +690,6 @@ eval_windowaggregates(WindowAggState *winstate)
temp_slot = winstate->temp_slot_1;
/*
- * Currently, we support only a subset of the SQL-standard window framing
- * rules.
- *
* If the frame start is UNBOUNDED_PRECEDING, the window frame consists of
* a contiguous group of rows extending forward from the start of the
* partition, and rows only enter the frame, never exit it, as the current
@@ -740,16 +741,17 @@ eval_windowaggregates(WindowAggState *winstate)
* the result values that were previously saved at the bottom of this
* function. Since we don't know the current frame's end yet, this is not
* possible to check for fully. But if the frame end mode is UNBOUNDED
- * FOLLOWING or CURRENT ROW, no exclusion clause is specified, and the
- * current row lies within the previous row's frame, then the two frames'
- * ends must coincide. Note that on the first row
- * aggregatedbase == aggregatedupto, meaning this test must fail, so we
+ * FOLLOWING or CURRENT ROW, no exclusion clause is specified, we are not
+ * in GROUPS BETWEEN with values mode, and the current row lies within the
+ * previous row's frame, then the two frames' ends must coincide. Note that on
+ * the first row aggregatedbase == aggregatedupto, meaning this test must fail, so we
* don't need to check the "there was no previous row" case explicitly here.
*/
if (winstate->aggregatedbase == winstate->frameheadpos &&
(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
FRAMEOPTION_END_CURRENT_ROW)) &&
!(winstate->frameOptions & FRAMEOPTION_EXCLUSION) &&
+ !(winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN) &&
winstate->aggregatedbase <= winstate->currentpos &&
winstate->aggregatedupto > winstate->currentpos)
{
@@ -771,6 +773,7 @@ eval_windowaggregates(WindowAggState *winstate)
* - if the frame's head moved and we cannot use an inverse
* transition function, or
* - we are in RANGE BETWEEN with values mode, or
+ * - we are in GROUPS BETWEEN with values mode, or
* - we are in EXCLUDE CURRENT ROW/EXCLUDE TIES mode, or
* - if the new frame doesn't overlap the old one
*
@@ -787,6 +790,7 @@ eval_windowaggregates(WindowAggState *winstate)
(winstate->aggregatedbase != winstate->frameheadpos &&
!OidIsValid(peraggstate->invtransfn_oid)) ||
winstate->frameOptions & FRAMEOPTION_RANGE_BETWEEN ||
+ winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN ||
winstate->frameOptions & FRAMEOPTION_EXCLUSION ||
winstate->aggregatedupto <= winstate->frameheadpos)
{
@@ -1132,6 +1136,7 @@ begin_partition(WindowAggState *winstate)
/* If the frame head is potentially movable ... */
if (!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) ||
winstate->frameOptions & (FRAMEOPTION_RANGE_BETWEEN) ||
+ winstate->frameOptions & (FRAMEOPTION_GROUPS_BETWEEN) ||
winstate->frameOptions & (FRAMEOPTION_EXCLUSION))
{
/* ... create a mark pointer to track the frame head */
@@ -1174,6 +1179,51 @@ begin_partition(WindowAggState *winstate)
*/
tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot);
winstate->spooled_rows++;
+
+ /*
+ * In GROUPS BETWEEN with values mode, we pre-compute the lengths of each
+ * window group by loading the partition and checking if each row is a peer
+ * of the succeeding row. The number of groups is stored in winobj->winGroupsCount
+ * and the number of rows in each group is stored in winobj->winGroupLen.
+ */
+ if (winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN)
+ {
+ int64 i,
+ partitionSize;
+ int64 *lenptr;
+ int64 groupLenSize = 16;
+ bool peers;
+
+ winstate->agg_winobj->winGroupsCount = 1;
+ winstate->agg_winobj->winGroupLen = palloc0(sizeof(int64) * groupLenSize);
+ lenptr = winstate->agg_winobj->winGroupLen;
+ *lenptr = 1;
+ partitionSize = WinGetPartitionRowCount(winstate->agg_winobj);
+
+ for (i = 0; i < partitionSize - 1; i++)
+ {
+ peers = WinRowsArePeers(winstate->agg_winobj, i, i+1);
+ if (peers)
+ (*lenptr)++;
+ else
+ {
+ winstate->agg_winobj->winGroupsCount++;
+ if (winstate->agg_winobj->winGroupsCount > groupLenSize)
+ {
+ int64 prevSize = groupLenSize;
+
+ groupLenSize *= 2;
+ winstate->agg_winobj->winGroupLen = repalloc(winstate->agg_winobj->winGroupLen, sizeof(int64) * groupLenSize);
+ lenptr = winstate->agg_winobj->winGroupLen;
+ lenptr += prevSize;
+ }
+ else
+ lenptr++;
+
+ *lenptr = 1;
+ }
+ }
+ }
}
/*
@@ -1284,6 +1334,71 @@ release_partition(WindowAggState *winstate)
tuplestore_end(winstate->buffer);
winstate->buffer = NULL;
winstate->partition_spooled = false;
+
+ if (winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN &&
+ winstate->agg_winobj->winGroupsCount > 0)
+ {
+ pfree(winstate->agg_winobj->winGroupLen);
+ winstate->agg_winobj->winGroupsCount = 0;
+ }
+}
+
+/*
+ * row_is_in_group
+ * Determine whether a row is in range when in GROUPS BETWEEN with values
+ * mode.
+ *
+ * Compares the current position to the slot position and checks if they are
+ * within the specified window group offset.
+ */
+static bool row_is_in_group(WindowObject winobj, int64 currpos, int64 slotpos,
+ int64 offset, bool preceding, bool end)
+{
+ int64 i,
+ len = 0,
+ currGroup = 0,
+ slotGroup = 0;
+ int64 *lenptr = winobj->winGroupLen;
+
+ if (preceding)
+ offset = -offset;
+ /*
+ * Calculate the currpos window group, then the slotpos window group. If
+ * the slotpos group is outside of the offset bounds, return false.
+ */
+ for (i = 0; i < winobj->winGroupsCount; i++, lenptr++)
+ {
+ len += *lenptr;
+ if (len > currpos)
+ {
+ currGroup = i;
+ break;
+ }
+ }
+ lenptr = winobj->winGroupLen;
+ len = 0;
+ for (i = 0; i < winobj->winGroupsCount; i++, lenptr++)
+ {
+ len += *lenptr;
+ if (len > slotpos)
+ {
+ slotGroup = i;
+ break;
+ }
+ }
+
+ currGroup += offset;
+ if (end)
+ {
+ if (slotGroup > currGroup)
+ return false;
+ }
+ else
+ {
+ if (slotGroup < currGroup)
+ return false;
+ }
+ return true;
}
/*
@@ -1445,7 +1560,7 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
if (pos < winstate->currentpos)
return -1;
}
- else if (frameOptions & FRAMEOPTION_RANGE)
+ else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
/* preceding row that is not peer is out of frame */
if (pos < winstate->currentpos &&
@@ -1483,6 +1598,15 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
slotval, winstate->startOffsetValue, preceding, false))
return 0;
}
+ else if (frameOptions & FRAMEOPTION_GROUPS)
+ {
+ bool preceding;
+
+ preceding = (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) != 0 ? true : false;
+ if (!row_is_in_group(winstate->agg_winobj,
+ winstate->currentpos, pos, offset, preceding, false))
+ return 0;
+ }
else
Assert(false);
}
@@ -1496,7 +1620,7 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
if (pos > winstate->currentpos)
return -1;
}
- else if (frameOptions & FRAMEOPTION_RANGE)
+ else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
/* following row that is not peer is out of frame */
if (pos > winstate->currentpos &&
@@ -1533,6 +1657,15 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
slotval, winstate->endOffsetValue, preceding, true))
return -1;
}
+ else if (frameOptions & FRAMEOPTION_GROUPS)
+ {
+ bool preceding;
+
+ preceding = (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) != 0 ? true : false;
+ if (!row_is_in_group(winstate->agg_winobj,
+ winstate->currentpos, pos, offset, preceding, true))
+ return -1;
+ }
else
Assert(false);
}
@@ -1542,7 +1675,12 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
{
if (pos == winstate->currentpos)
return 0;
- } else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
+ } else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
+ {
+ if (are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
+ return 0;
+ }
+ else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
{
if ((pos != winstate->currentpos) &&
are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
@@ -1586,7 +1724,7 @@ update_frameheadpos(WindowObject winobj, TupleTableSlot *slot)
winstate->frameheadpos = winstate->currentpos;
winstate->framehead_valid = true;
}
- else if (frameOptions & FRAMEOPTION_RANGE)
+ else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
int64 fhprev;
@@ -1646,7 +1784,7 @@ update_frameheadpos(WindowObject winobj, TupleTableSlot *slot)
}
winstate->framehead_valid = true;
}
- else if (frameOptions & FRAMEOPTION_RANGE)
+ else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
winstate->frameheadpos = 0;
winstate->framehead_valid = true;
@@ -1692,7 +1830,7 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
winstate->frametailpos = winstate->currentpos;
winstate->frametail_valid = true;
}
- else if (frameOptions & FRAMEOPTION_RANGE)
+ else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
int64 ftnext;
@@ -1748,7 +1886,7 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
}
winstate->frametail_valid = true;
}
- else if (frameOptions & FRAMEOPTION_RANGE)
+ else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
{
winstate->frametailpos = -1;
winstate->frametail_valid = true;
@@ -1809,7 +1947,7 @@ ExecWindowAgg(PlanState *pstate)
get_typlenbyval(exprType((Node *) winstate->startOffset->expr),
&len, &byval);
winstate->startOffsetValue = datumCopy(value, byval, len);
- if (frameOptions & FRAMEOPTION_ROWS)
+ if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
{
/* value is known to be int8 */
int64 offset = DatumGetInt64(value);
@@ -1834,7 +1972,7 @@ ExecWindowAgg(PlanState *pstate)
get_typlenbyval(exprType((Node *) winstate->endOffset->expr),
&len, &byval);
winstate->endOffsetValue = datumCopy(value, byval, len);
- if (frameOptions & FRAMEOPTION_ROWS)
+ if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS))
{
/* value is known to be int8 */
int64 offset = DatumGetInt64(value);
@@ -1852,6 +1990,7 @@ ExecWindowAgg(PlanState *pstate)
{
/* Initialize for first partition and set current row = 0 */
begin_partition(winstate);
+
/* If there are no input rows, we'll detect that and exit below */
}
else
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d54fbff545..917341f1c5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -632,7 +632,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
- GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING
+ GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
HANDLER HAVING HEADER_P HOLD HOUR_P
@@ -725,8 +725,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* various unreserved keywords as needed to resolve ambiguities (this can't
* have any bad effects since obviously the keywords will still behave the
* same as if they weren't keywords). We need to do this for PARTITION,
- * RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
- * so that they can follow a_expr without creating postfix-operator problems;
+ * RANGE, ROWS, or GROUPS to support opt_existing_window_name; and for RANGE, ROWS
+ * or GROUPS so that they can follow a_expr without creating postfix-operator problems;
* for GENERATED so that it can follow b_expr;
* and for NULL so that it can follow b_expr in ColQualList without creating
* postfix-operator problems.
@@ -746,7 +746,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* blame any funny behavior of UNBOUNDED on the SQL standard, though.
*/
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
-%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@@ -13981,7 +13981,7 @@ window_specification: '(' opt_existing_window_name opt_partition_clause
;
/*
- * If we see PARTITION, RANGE, or ROWS as the first token after the '('
+ * If we see PARTITION, RANGE, ROWS or GROUPS as the first token after the '('
* of a window_specification, we want the assumption to be that there is
* no existing_window_name; but those keywords are unreserved and so could
* be ColIds. We fix this by making them have the same precedence as IDENT
@@ -14001,9 +14001,6 @@ opt_partition_clause: PARTITION BY expr_list { $$ = $3; }
/*
* For frame clauses, we return a WindowDef, but only some fields are used:
* frameOptions, startOffset, and endOffset.
- *
- * This is only a subset of the full SQL:2011 frame_clause grammar.
- * We don't support GROUPS yet.
*/
opt_frame_clause:
RANGE frame_extent opt_window_exclusion_clause
@@ -14029,6 +14026,20 @@ opt_frame_clause:
n->frameOptions |= n2->frameOptions;
$$ = n;
}
+ | GROUPS frame_extent opt_window_exclusion_clause
+ {
+ WindowDef *n = $2;
+ WindowDef *n2 = $3;
+ n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_GROUPS;
+ if ((n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING |
+ FRAMEOPTION_END_VALUE_PRECEDING)) ||
+ (n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING |
+ FRAMEOPTION_END_VALUE_FOLLOWING)))
+ n->frameOptions |= FRAMEOPTION_GROUPS_BETWEEN;
+ if (n2 != NULL)
+ n->frameOptions |= n2->frameOptions;
+ $$ = n;
+ }
| /*EMPTY*/
{
WindowDef *n = makeNode(WindowDef);
@@ -14150,6 +14161,12 @@ opt_window_exclusion_clause:
n->frameOptions = FRAMEOPTION_EXCLUDE_CURRENT;
$$ = n;
}
+ | EXCLUDE GROUP_P
+ {
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_EXCLUDE_GROUP;
+ $$ = n;
+ }
| EXCLUDE TIES
{
WindowDef *n = makeNode(WindowDef);
@@ -15026,6 +15043,7 @@ unreserved_keyword:
| GENERATED
| GLOBAL
| GRANTED
+ | GROUPS
| HANDLER
| HEADER_P
| HOLD
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 4c4f4cdc3d..0922a9a7c3 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -420,6 +420,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in window ROWS");
break;
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in window GROUPS");
+ else
+ err = _("grouping operations are not allowed in window GROUPS");
+
+ break;
case EXPR_KIND_SELECT_TARGET:
/* okay */
break;
@@ -835,6 +842,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
err = _("window functions are not allowed in window definitions");
break;
case EXPR_KIND_SELECT_TARGET:
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 73c80bbf1c..7d92a8edfd 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -3588,6 +3588,17 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
else
node = coerce_to_specific_type(pstate, node, INT8OID, constructName);
}
+ else if (frameOptions & FRAMEOPTION_GROUPS)
+ {
+ /* Transform the raw expression tree */
+ node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_GROUPS);
+
+ /*
+ * Like LIMIT clause, simply coerce to int8
+ */
+ constructName = "GROUPS";
+ node = coerce_to_specific_type(pstate, node, INT8OID, constructName);
+ }
else
{
Assert(false);
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 29f9da796f..a1f44b0e7a 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1805,6 +1805,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_WINDOW_ORDER:
case EXPR_KIND_WINDOW_FRAME_RANGE:
case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
case EXPR_KIND_SELECT_TARGET:
case EXPR_KIND_INSERT_TARGET:
case EXPR_KIND_UPDATE_SOURCE:
@@ -3428,6 +3429,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "window RANGE";
case EXPR_KIND_WINDOW_FRAME_ROWS:
return "window ROWS";
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
+ return "window GROUPS";
case EXPR_KIND_SELECT_TARGET:
return "SELECT";
case EXPR_KIND_INSERT_TARGET:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 2f20516e76..9b8413bf26 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2218,6 +2218,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
break;
case EXPR_KIND_WINDOW_FRAME_RANGE:
case EXPR_KIND_WINDOW_FRAME_ROWS:
+ case EXPR_KIND_WINDOW_FRAME_GROUPS:
err = _("set-returning functions are not allowed in window definitions");
break;
case EXPR_KIND_SELECT_TARGET:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 867a4a26a8..85dc7d654c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5874,6 +5874,8 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
appendStringInfoString(buf, "RANGE ");
else if (wc->frameOptions & FRAMEOPTION_ROWS)
appendStringInfoString(buf, "ROWS ");
+ else if (wc->frameOptions & FRAMEOPTION_GROUPS)
+ appendStringInfoString(buf, "GROUPS ");
else
Assert(false);
if (wc->frameOptions & FRAMEOPTION_BETWEEN)
@@ -5916,6 +5918,8 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
}
if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT)
appendStringInfoString(buf, "EXCLUDE CURRENT ROW ");
+ else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
+ appendStringInfoString(buf, "EXCLUDE GROUP ");
else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES)
appendStringInfoString(buf, "EXCLUDE TIES ");
else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_NO_OTHERS)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5cfce277cc..626c1864a9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -499,26 +499,28 @@ typedef struct WindowDef
* which were defaulted; the correct behavioral bits must be set either way.
* The START_foo and END_foo options must come in pairs of adjacent bits for
* the convenience of gram.y, even though some of them are useless/invalid.
- * We will need more bits (and fields) to cover the full SQL:2008 option set.
*/
#define FRAMEOPTION_NONDEFAULT 0x00001 /* any specified? */
#define FRAMEOPTION_RANGE 0x00002 /* RANGE behavior */
#define FRAMEOPTION_ROWS 0x00004 /* ROWS behavior */
-#define FRAMEOPTION_BETWEEN 0x00008 /* BETWEEN given? */
-#define FRAMEOPTION_START_UNBOUNDED_PRECEDING 0x00010 /* start is U. P. */
-#define FRAMEOPTION_END_UNBOUNDED_PRECEDING 0x00020 /* (disallowed) */
-#define FRAMEOPTION_START_UNBOUNDED_FOLLOWING 0x00040 /* (disallowed) */
-#define FRAMEOPTION_END_UNBOUNDED_FOLLOWING 0x00080 /* end is U. F. */
-#define FRAMEOPTION_START_CURRENT_ROW 0x00100 /* start is C. R. */
-#define FRAMEOPTION_END_CURRENT_ROW 0x00200 /* end is C. R. */
-#define FRAMEOPTION_START_VALUE_PRECEDING 0x00400 /* start is V. P. */
-#define FRAMEOPTION_END_VALUE_PRECEDING 0x00800 /* end is V. P. */
-#define FRAMEOPTION_START_VALUE_FOLLOWING 0x01000 /* start is V. F. */
-#define FRAMEOPTION_END_VALUE_FOLLOWING 0x02000 /* end is V. F. */
-#define FRAMEOPTION_RANGE_BETWEEN 0x04000 /* RANGE BETWEEN with values */
-#define FRAMEOPTION_EXCLUDE_CURRENT 0x08000 /* exclude current row */
-#define FRAMEOPTION_EXCLUDE_TIES 0x10000 /* exclude ties */
-#define FRAMEOPTION_EXCLUDE_NO_OTHERS 0x20000 /* exclude no others */
+#define FRAMEOPTION_GROUPS 0x00008 /* GROUPS behavior */
+#define FRAMEOPTION_BETWEEN 0x00010 /* BETWEEN given? */
+#define FRAMEOPTION_START_UNBOUNDED_PRECEDING 0x00020 /* start is U. P. */
+#define FRAMEOPTION_END_UNBOUNDED_PRECEDING 0x00040 /* (disallowed) */
+#define FRAMEOPTION_START_UNBOUNDED_FOLLOWING 0x00080 /* (disallowed) */
+#define FRAMEOPTION_END_UNBOUNDED_FOLLOWING 0x00100 /* end is U. F. */
+#define FRAMEOPTION_START_CURRENT_ROW 0x00200 /* start is C. R. */
+#define FRAMEOPTION_END_CURRENT_ROW 0x00400 /* end is C. R. */
+#define FRAMEOPTION_START_VALUE_PRECEDING 0x00800 /* start is V. P. */
+#define FRAMEOPTION_END_VALUE_PRECEDING 0x01000 /* end is V. P. */
+#define FRAMEOPTION_START_VALUE_FOLLOWING 0x02000 /* start is V. F. */
+#define FRAMEOPTION_END_VALUE_FOLLOWING 0x04000 /* end is V. F. */
+#define FRAMEOPTION_RANGE_BETWEEN 0x08000 /* RANGE BETWEEN with values */
+#define FRAMEOPTION_GROUPS_BETWEEN 0x010000 /* GROUPS BETWEEN with values */
+#define FRAMEOPTION_EXCLUDE_CURRENT 0x020000 /* exclude current row */
+#define FRAMEOPTION_EXCLUDE_TIES 0x040000 /* exclude ties */
+#define FRAMEOPTION_EXCLUDE_NO_OTHERS 0x080000 /* exclude no others */
+#define FRAMEOPTION_EXCLUDE_GROUP 0x100000 /* exclude group */
#define FRAMEOPTION_START_VALUE \
(FRAMEOPTION_START_VALUE_PRECEDING | FRAMEOPTION_START_VALUE_FOLLOWING)
@@ -530,7 +532,8 @@ typedef struct WindowDef
FRAMEOPTION_END_CURRENT_ROW)
#define FRAMEOPTION_EXCLUSION \
- (FRAMEOPTION_EXCLUDE_CURRENT | FRAMEOPTION_EXCLUDE_TIES)
+ (FRAMEOPTION_EXCLUDE_CURRENT | FRAMEOPTION_EXCLUDE_TIES | \
+ FRAMEOPTION_EXCLUDE_GROUP)
/*
* RangeSubselect - subquery appearing in a FROM clause
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 87c4cffba5..b51f86fd28 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -182,6 +182,7 @@ PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD)
+PG_KEYWORD("groups", GROUPS, UNRESERVED_KEYWORD)
PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 565bb3dc6c..1141d8aa2f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -45,6 +45,7 @@ typedef enum ParseExprKind
EXPR_KIND_WINDOW_ORDER, /* window definition ORDER BY */
EXPR_KIND_WINDOW_FRAME_RANGE, /* window frame clause with RANGE */
EXPR_KIND_WINDOW_FRAME_ROWS, /* window frame clause with ROWS */
+ EXPR_KIND_WINDOW_FRAME_GROUPS, /* window frame clause with GROUPS */
EXPR_KIND_SELECT_TARGET, /* SELECT target list item */
EXPR_KIND_INSERT_TARGET, /* INSERT target list item */
EXPR_KIND_UPDATE_SOURCE, /* UPDATE assignment source item */
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index d8ae3807a2..b8220d44d2 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -858,6 +858,23 @@ FROM tenk1 WHERE unique1 < 10;
10 | 0 | 0
(10 rows)
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 4 | 0
+ | 2 | 2
+ | 1 | 1
+ | 6 | 2
+ | 9 | 1
+ | 8 | 0
+ | 5 | 1
+ | 3 | 3
+ | 7 | 3
+ | 0 | 0
+(10 rows)
+
SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties),
unique1, four
FROM tenk1 WHERE unique1 < 10;
@@ -960,6 +977,23 @@ FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
38 | 7 | 3
(10 rows)
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 35 | 3 | 3
+ 35 | 7 | 3
+(10 rows)
+
SELECT sum(unique1) over (w range between unbounded preceding and current row exclude ties),
unique1, four
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
@@ -1069,6 +1103,32 @@ SELECT pg_get_viewdef('v_window');
CREATE OR REPLACE TEMP VIEW v_window AS
SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+ exclude group) as sum_rows FROM generate_series(1, 10) i;
+SELECT * FROM v_window;
+ i | sum_rows
+----+----------
+ 1 | 2
+ 2 | 4
+ 3 | 6
+ 4 | 8
+ 5 | 10
+ 6 | 12
+ 7 | 14
+ 8 | 16
+ 9 | 18
+ 10 | 9
+(10 rows)
+
+SELECT pg_get_viewdef('v_window');
+ pg_get_viewdef
+-----------------------------------------------------------------------------------------------------
+ SELECT i.i, +
+ sum(i.i) OVER (ORDER BY i.i ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE GROUP) AS sum_rows+
+ FROM generate_series(1, 10) i(i);
+(1 row)
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+ SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
exclude ties) as sum_rows FROM generate_series(1, 10) i;
SELECT * FROM v_window;
i | sum_rows
@@ -1119,6 +1179,32 @@ SELECT pg_get_viewdef('v_window');
FROM generate_series(1, 10) i(i);
(1 row)
+CREATE OR REPLACE TEMP VIEW v_window AS
+ SELECT i, sum(i) over (order by i groups between 1 preceding and 1 following
+ exclude no others) as sum_rows FROM generate_series(1, 10) i;
+SELECT * FROM v_window;
+ i | sum_rows
+----+----------
+ 1 | 3
+ 2 | 6
+ 3 | 9
+ 4 | 12
+ 5 | 15
+ 6 | 18
+ 7 | 21
+ 8 | 24
+ 9 | 27
+ 10 | 19
+(10 rows)
+
+SELECT pg_get_viewdef('v_window');
+ pg_get_viewdef
+-----------------------------------------------------------------------------------------------------------
+ SELECT i.i, +
+ sum(i.i) OVER (ORDER BY i.i GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE NO OTHERS) AS sum_rows+
+ FROM generate_series(1, 10) i(i);
+(1 row)
+
-- RANGE BETWEEN with values tests
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
unique1, four
@@ -1171,6 +1257,23 @@ FROM tenk1 WHERE unique1 < 10;
23 | 7 | 3
(10 rows)
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude ties),
unique1, four
FROM tenk1 WHERE unique1 < 10;
@@ -1205,6 +1308,23 @@ FROM tenk1 WHERE unique1 < 10;
30 | 7 | 3
(10 rows)
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 33 | 0 | 0
+ 33 | 8 | 0
+ 33 | 4 | 0
+ 30 | 5 | 1
+ 30 | 9 | 1
+ 30 | 1 | 1
+ 37 | 6 | 2
+ 37 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
@@ -1272,6 +1392,22 @@ select sum(salary) over (order by enroll_date range between '1 year'::interval p
(10 rows)
select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+ exclude group), salary, enroll_date from empsalary;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 23900 | 5000 | 10-01-2006
+ 23900 | 6000 | 10-01-2006
+ 34500 | 3900 | 12-23-2006
+ 37100 | 4800 | 08-01-2007
+ 37100 | 5200 | 08-01-2007
+ 42300 | 4800 | 08-08-2007
+ 41900 | 5200 | 08-15-2007
+ 32600 | 3500 | 12-10-2007
+ 23500 | 4500 | 01-01-2008
+ 23500 | 4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
exclude ties), salary, enroll_date from empsalary;
sum | salary | enroll_date
-------+--------+-------------
@@ -1320,6 +1456,22 @@ select sum(salary) over (order by enroll_time range between '1 hour'::interval p
(10 rows)
select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude group), salary, enroll_time from empsalary;
+ sum | salary | enroll_time
+-------+--------+-------------
+ 8500 | 5200 | 11:00:00
+ 13500 | 5000 | 12:00:00
+ 17900 | 3500 | 13:00:00
+ 11600 | 4800 | 14:00:00
+ 9300 | 3900 | 15:00:00
+ 9300 | 4200 | 15:00:00
+ 10800 | 4500 | 17:00:00
+ 15700 | 4800 | 18:00:00
+ 10000 | 6000 | 19:00:00
+ 6000 | 5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
exclude ties), salary, enroll_time from empsalary;
sum | salary | enroll_time
-------+--------+-------------
@@ -1368,6 +1520,22 @@ select sum(salary) over (order by enroll_timetz range between '1 hour'::interval
(10 rows)
select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude group), salary, enroll_timetz from empsalary;
+ sum | salary | enroll_timetz
+-------+--------+---------------
+ 8500 | 5200 | 11:00:00+01
+ 13500 | 5000 | 12:00:00+01
+ 17900 | 3500 | 13:00:00+01
+ 11600 | 4800 | 14:00:00+01
+ 9300 | 3900 | 15:00:00+01
+ 9300 | 4200 | 15:00:00+01
+ 10800 | 4500 | 17:00:00+01
+ 15700 | 4800 | 18:00:00+01
+ 10000 | 6000 | 19:00:00+01
+ 6000 | 5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
exclude ties), salary, enroll_timetz from empsalary;
sum | salary | enroll_timetz
-------+--------+---------------
@@ -1416,6 +1584,22 @@ select sum(salary) over (order by enroll_interval range between '1 year'::interv
(10 rows)
select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+ exclude group), salary, enroll_interval from empsalary;
+ sum | salary | enroll_interval
+-------+--------+-----------------
+ 8500 | 5200 | @ 1 year
+ 13500 | 5000 | @ 2 years
+ 17900 | 3500 | @ 3 years
+ 11600 | 4800 | @ 4 years
+ 9300 | 3900 | @ 5 years
+ 9300 | 4200 | @ 5 years
+ 10800 | 4500 | @ 7 years
+ 15700 | 4800 | @ 8 years
+ 10000 | 6000 | @ 9 years
+ 6000 | 5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_interval from empsalary;
sum | salary | enroll_interval
-------+--------+-----------------
@@ -1464,6 +1648,22 @@ select sum(salary) over (order by enroll_timestamptz range between '1 year'::int
(10 rows)
select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+ exclude group), salary, enroll_timestamptz from empsalary;
+ sum | salary | enroll_timestamptz
+-------+--------+------------------------------
+ 13300 | 5200 | Thu Oct 19 10:23:54 2000 PDT
+ 13900 | 5000 | Fri Oct 19 10:23:54 2001 PDT
+ 13900 | 3500 | Fri Oct 19 10:23:54 2001 PDT
+ 16600 | 4800 | Sat Oct 19 10:23:54 2002 PDT
+ 13500 | 3900 | Sun Oct 19 10:23:54 2003 PDT
+ 13200 | 4200 | Tue Oct 19 10:23:54 2004 PDT
+ 15000 | 4500 | Wed Oct 19 10:23:54 2005 PDT
+ 15700 | 4800 | Thu Oct 19 10:23:54 2006 PDT
+ 10000 | 6000 | Fri Oct 19 10:23:54 2007 PDT
+ 6000 | 5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_timestamptz from empsalary;
sum | salary | enroll_timestamptz
-------+--------+------------------------------
@@ -1512,6 +1712,22 @@ select sum(salary) over (order by enroll_timestamp range between '1 year'::inter
(10 rows)
select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+ exclude group), salary, enroll_timestamp from empsalary;
+ sum | salary | enroll_timestamp
+-------+--------+--------------------------
+ 13300 | 5200 | Thu Oct 19 10:23:54 2000
+ 13900 | 5000 | Fri Oct 19 10:23:54 2001
+ 13900 | 3500 | Fri Oct 19 10:23:54 2001
+ 16600 | 4800 | Sat Oct 19 10:23:54 2002
+ 13500 | 3900 | Sun Oct 19 10:23:54 2003
+ 13200 | 4200 | Tue Oct 19 10:23:54 2004
+ 15000 | 4500 | Wed Oct 19 10:23:54 2005
+ 15700 | 4800 | Thu Oct 19 10:23:54 2006
+ 10000 | 6000 | Fri Oct 19 10:23:54 2007
+ 6000 | 5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_timestamp from empsalary;
sum | salary | enroll_timestamp
-------+--------+--------------------------
@@ -1592,6 +1808,22 @@ select sum(salary) over(order by enroll_date range between unbounded preceding a
(10 rows)
select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+ exclude group), salary, enroll_date from empsalary;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 23900 | 5000 | 10-01-2006
+ 23900 | 6000 | 10-01-2006
+ 34500 | 3900 | 12-23-2006
+ 37100 | 4800 | 08-01-2007
+ 37100 | 5200 | 08-01-2007
+ 42300 | 4800 | 08-08-2007
+ 41900 | 5200 | 08-15-2007
+ 43600 | 3500 | 12-10-2007
+ 38400 | 4500 | 01-01-2008
+ 38400 | 4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
exclude ties), salary, enroll_date from empsalary;
sum | salary | enroll_date
-------+--------+-------------
@@ -1637,6 +1869,475 @@ select max(enroll_date) over (order by salary range between '1 year'::interval p
ERROR: Offsets must be an integral
LINE 1: select max(enroll_date) over (order by salary range between ...
^
+-- GROUPS tests
+SELECT sum(unique1) over (order by four groups between unbounded preceding and current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 12 | 0 | 0
+ 12 | 8 | 0
+ 12 | 4 | 0
+ 27 | 5 | 1
+ 27 | 9 | 1
+ 27 | 1 | 1
+ 35 | 6 | 2
+ 35 | 2 | 2
+ 45 | 3 | 3
+ 45 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 45 | 5 | 1
+ 45 | 9 | 1
+ 45 | 1 | 1
+ 45 | 6 | 2
+ 45 | 2 | 2
+ 45 | 3 | 3
+ 45 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between current row and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 33 | 5 | 1
+ 33 | 9 | 1
+ 33 | 1 | 1
+ 18 | 6 | 2
+ 18 | 2 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 45 | 0 | 0
+ 45 | 8 | 0
+ 45 | 4 | 0
+ 45 | 5 | 1
+ 45 | 9 | 1
+ 45 | 1 | 1
+ 33 | 6 | 2
+ 33 | 2 | 2
+ 18 | 3 | 3
+ 18 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 1 following and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 33 | 0 | 0
+ 33 | 8 | 0
+ 33 | 4 | 0
+ 18 | 5 | 1
+ 18 | 9 | 1
+ 18 | 1 | 1
+ 10 | 6 | 2
+ 10 | 2 | 2
+ | 3 | 3
+ | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and 2 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 35 | 0 | 0
+ 35 | 8 | 0
+ 35 | 4 | 0
+ 45 | 5 | 1
+ 45 | 9 | 1
+ 45 | 1 | 1
+ 45 | 6 | 2
+ 45 | 2 | 2
+ 45 | 3 | 3
+ 45 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 27 | 0 | 0
+ 27 | 8 | 0
+ 27 | 4 | 0
+ 35 | 5 | 1
+ 35 | 9 | 1
+ 35 | 1 | 1
+ 45 | 6 | 2
+ 45 | 2 | 2
+ 33 | 3 | 3
+ 33 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 12 | 0 | 0
+ 12 | 8 | 0
+ 12 | 4 | 0
+ 15 | 5 | 1
+ 15 | 9 | 1
+ 15 | 1 | 1
+ 8 | 6 | 2
+ 8 | 2 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude current row), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 27 | 0 | 0
+ 19 | 8 | 0
+ 23 | 4 | 0
+ 30 | 5 | 1
+ 26 | 9 | 1
+ 34 | 1 | 1
+ 39 | 6 | 2
+ 43 | 2 | 2
+ 30 | 3 | 3
+ 26 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude group), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 15 | 0 | 0
+ 15 | 8 | 0
+ 15 | 4 | 0
+ 20 | 5 | 1
+ 20 | 9 | 1
+ 20 | 1 | 1
+ 37 | 6 | 2
+ 37 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude ties), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 15 | 0 | 0
+ 23 | 8 | 0
+ 19 | 4 | 0
+ 25 | 5 | 1
+ 29 | 9 | 1
+ 21 | 1 | 1
+ 43 | 6 | 2
+ 39 | 2 | 2
+ 26 | 3 | 3
+ 30 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following),unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten
+-----+---------+------+-----
+ 0 | 0 | 0 | 0
+ 1 | 1 | 1 | 1
+ 2 | 2 | 2 | 2
+ 3 | 3 | 3 | 3
+ 4 | 4 | 0 | 4
+ 5 | 5 | 1 | 5
+ 6 | 6 | 2 | 6
+ 7 | 7 | 3 | 7
+ 8 | 8 | 0 | 8
+ 9 | 9 | 1 | 9
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following exclude current row), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten
+-----+---------+------+-----
+ | 0 | 0 | 0
+ | 1 | 1 | 1
+ | 2 | 2 | 2
+ | 3 | 3 | 3
+ | 4 | 0 | 4
+ | 5 | 1 | 5
+ | 6 | 2 | 6
+ | 7 | 3 | 7
+ | 8 | 0 | 8
+ | 9 | 1 | 9
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following exclude group), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten
+-----+---------+------+-----
+ | 0 | 0 | 0
+ | 1 | 1 | 1
+ | 2 | 2 | 2
+ | 3 | 3 | 3
+ | 4 | 0 | 4
+ | 5 | 1 | 5
+ | 6 | 2 | 6
+ | 7 | 3 | 7
+ | 8 | 0 | 8
+ | 9 | 1 | 9
+(10 rows)
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following exclude ties), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four | ten
+-----+---------+------+-----
+ 0 | 0 | 0 | 0
+ 1 | 1 | 1 | 1
+ 2 | 2 | 2 | 2
+ 3 | 3 | 3 | 3
+ 4 | 4 | 0 | 4
+ 5 | 5 | 1 | 5
+ 6 | 6 | 2 | 6
+ 7 | 7 | 3 | 7
+ 8 | 8 | 0 | 8
+ 9 | 9 | 1 | 9
+(10 rows)
+
+-- Show differences in values mode between ROWS, RANGE, and GROUPS
+WITH cte (x) AS (
+ SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+ x | sum
+----+-----
+ 1 | 4
+ 3 | 9
+ 5 | 15
+ 7 | 21
+ 9 | 27
+ 11 | 33
+ 13 | 39
+ 15 | 45
+ 17 | 51
+ 19 | 57
+ 21 | 63
+ 23 | 69
+ 25 | 75
+ 27 | 81
+ 29 | 87
+ 31 | 93
+ 33 | 99
+ 35 | 68
+(18 rows)
+
+WITH cte (x) AS (
+ SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+ x | sum
+----+-----
+ 1 | 1
+ 3 | 3
+ 5 | 5
+ 7 | 7
+ 9 | 9
+ 11 | 11
+ 13 | 13
+ 15 | 15
+ 17 | 17
+ 19 | 19
+ 21 | 21
+ 23 | 23
+ 25 | 25
+ 27 | 27
+ 29 | 29
+ 31 | 31
+ 33 | 33
+ 35 | 35
+(18 rows)
+
+WITH cte (x) AS (
+ SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+ x | sum
+----+-----
+ 1 | 4
+ 3 | 9
+ 5 | 15
+ 7 | 21
+ 9 | 27
+ 11 | 33
+ 13 | 39
+ 15 | 45
+ 17 | 51
+ 19 | 57
+ 21 | 63
+ 23 | 69
+ 25 | 75
+ 27 | 81
+ 29 | 87
+ 31 | 93
+ 33 | 99
+ 35 | 68
+(18 rows)
+
+WITH cte (x) AS (
+ select 1 union all select 1 union all select 1 union all
+ SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+ x | sum
+----+-----
+ 1 | 2
+ 1 | 3
+ 1 | 7
+ 5 | 13
+ 7 | 21
+ 9 | 27
+ 11 | 33
+ 13 | 39
+ 15 | 45
+ 17 | 51
+ 19 | 57
+ 21 | 63
+ 23 | 69
+ 25 | 75
+ 27 | 81
+ 29 | 87
+ 31 | 93
+ 33 | 99
+ 35 | 105
+ 37 | 111
+ 39 | 117
+ 41 | 123
+ 43 | 129
+ 45 | 135
+ 47 | 141
+ 49 | 96
+(26 rows)
+
+WITH cte (x) AS (
+ select 1 union all select 1 union all select 1 union all
+ SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+ x | sum
+----+-----
+ 1 | 3
+ 1 | 3
+ 1 | 3
+ 5 | 5
+ 7 | 7
+ 9 | 9
+ 11 | 11
+ 13 | 13
+ 15 | 15
+ 17 | 17
+ 19 | 19
+ 21 | 21
+ 23 | 23
+ 25 | 25
+ 27 | 27
+ 29 | 29
+ 31 | 31
+ 33 | 33
+ 35 | 35
+ 37 | 37
+ 39 | 39
+ 41 | 41
+ 43 | 43
+ 45 | 45
+ 47 | 47
+ 49 | 49
+(26 rows)
+
+WITH cte (x) AS (
+ select 1 union all select 1 union all select 1 union all
+ SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+ x | sum
+----+-----
+ 1 | 8
+ 1 | 8
+ 1 | 8
+ 5 | 15
+ 7 | 21
+ 9 | 27
+ 11 | 33
+ 13 | 39
+ 15 | 45
+ 17 | 51
+ 19 | 57
+ 21 | 63
+ 23 | 69
+ 25 | 75
+ 27 | 81
+ 29 | 87
+ 31 | 93
+ 33 | 99
+ 35 | 105
+ 37 | 111
+ 39 | 117
+ 41 | 123
+ 43 | 129
+ 45 | 135
+ 47 | 141
+ 49 | 96
+(26 rows)
+
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
count
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index cd25200501..87346fcf0f 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -202,6 +202,10 @@ SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude curre
unique1, four
FROM tenk1 WHERE unique1 < 10;
+SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude ties),
unique1, four
FROM tenk1 WHERE unique1 < 10;
@@ -226,6 +230,10 @@ SELECT sum(unique1) over (w range between unbounded preceding and current row ex
unique1, four
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+SELECT sum(unique1) over (w range between unbounded preceding and current row exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
+
SELECT sum(unique1) over (w range between unbounded preceding and current row exclude ties),
unique1, four
FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four);
@@ -260,6 +268,14 @@ SELECT pg_get_viewdef('v_window');
CREATE OR REPLACE TEMP VIEW v_window AS
SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
+ exclude group) as sum_rows FROM generate_series(1, 10) i;
+
+SELECT * FROM v_window;
+
+SELECT pg_get_viewdef('v_window');
+
+CREATE OR REPLACE TEMP VIEW v_window AS
+ SELECT i, sum(i) over (order by i rows between 1 preceding and 1 following
exclude ties) as sum_rows FROM generate_series(1, 10) i;
SELECT * FROM v_window;
@@ -274,6 +290,14 @@ SELECT * FROM v_window;
SELECT pg_get_viewdef('v_window');
+CREATE OR REPLACE TEMP VIEW v_window AS
+ SELECT i, sum(i) over (order by i groups between 1 preceding and 1 following
+ exclude no others) as sum_rows FROM generate_series(1, 10) i;
+
+SELECT * FROM v_window;
+
+SELECT pg_get_viewdef('v_window');
+
-- RANGE BETWEEN with values tests
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
unique1, four
@@ -287,6 +311,10 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::i
unique1, four
FROM tenk1 WHERE unique1 < 10;
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude ties),
unique1, four
FROM tenk1 WHERE unique1 < 10;
@@ -295,6 +323,10 @@ SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::i
unique1, four
FROM tenk1 WHERE unique1 < 10;
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude group),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following),
unique1, four
FROM tenk1 WHERE unique1 < 10;
@@ -310,6 +342,9 @@ select sum(salary) over (order by enroll_date range between '1 year'::interval p
exclude current row), salary, enroll_date from empsalary;
select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+ exclude group), salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
exclude ties), salary, enroll_date from empsalary;
select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following),
@@ -319,6 +354,9 @@ select sum(salary) over (order by enroll_time range between '1 hour'::interval p
exclude current row), salary, enroll_time from empsalary;
select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude group), salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
exclude ties), salary, enroll_time from empsalary;
select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following),
@@ -328,6 +366,9 @@ select sum(salary) over (order by enroll_timetz range between '1 hour'::interval
exclude current row), salary, enroll_timetz from empsalary;
select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude group), salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
exclude ties), salary, enroll_timetz from empsalary;
select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following),
@@ -337,6 +378,9 @@ select sum(salary) over (order by enroll_interval range between '1 year'::interv
exclude current row), salary, enroll_interval from empsalary;
select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+ exclude group), salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_interval from empsalary;
select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following),
@@ -346,6 +390,9 @@ select sum(salary) over (order by enroll_timestamptz range between '1 year'::int
exclude current row), salary, enroll_timestamptz from empsalary;
select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+ exclude group), salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_timestamptz from empsalary;
select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following),
@@ -355,6 +402,9 @@ select sum(salary) over (order by enroll_timestamp range between '1 year'::inter
exclude current row), salary, enroll_timestamp from empsalary;
select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+ exclude group), salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_timestamp from empsalary;
select sum(salary) over (order by enroll_timestamp range between current row and '2 years'::interval following),
@@ -370,6 +420,9 @@ select sum(salary) over(order by enroll_date range between unbounded preceding a
exclude current row), salary, enroll_date from empsalary;
select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
+ exclude group), salary, enroll_date from empsalary;
+
+select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following
exclude ties), salary, enroll_date from empsalary;
-- RANGE BETWEEN with values negative tests
@@ -394,6 +447,118 @@ select max(enroll_date) over (order by salary range between 1 preceding and -2 f
select max(enroll_date) over (order by salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_timestamp from empsalary;
+-- GROUPS tests
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between current row and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 1 preceding and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 1 following and unbounded following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between unbounded preceding and 2 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 0 preceding and 0 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude current row), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude group), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four groups between 2 preceding and 1 following
+ exclude ties), unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following),unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following exclude current row), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following exclude group), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by ten
+ order by four groups between 0 preceding and 0 following exclude ties), unique1, four, ten
+FROM tenk1 WHERE unique1 < 10;
+
+-- Show differences in values mode between ROWS, RANGE, and GROUPS
+WITH cte (x) AS (
+ SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+ SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+ SELECT * FROM generate_series(1, 35, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+ select 1 union all select 1 union all select 1 union all
+ SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x rows between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+ select 1 union all select 1 union all select 1 union all
+ SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
+
+WITH cte (x) AS (
+ select 1 union all select 1 union all select 1 union all
+ SELECT * FROM generate_series(5, 49, 2)
+)
+SELECT x, (sum(x) over w)
+FROM cte
+WINDOW w AS (ORDER BY x groups between 1 preceding and 1 following);
+
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;