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 8a3e86b6db..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 -{ RANGE | ROWS } BETWEEN frame_start AND frame_end +{ 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 @@ -874,6 +874,16 @@ CURRENT ROW UNBOUNDED FOLLOWING + and the optional frame_exclusion_clause can be + one of + + +EXCLUDE CURRENT ROW +EXCLUDE GROUP +EXCLUDE TIES +EXCLUDE NO OTHERS + + If frame_end is omitted it defaults to CURRENT ROW. Restrictions are that frame_start cannot be UNBOUNDED FOLLOWING, @@ -894,25 +904,39 @@ UNBOUNDED FOLLOWING 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 are currently only - allowed in ROWS mode. They indicate that the frame starts - or ends with the row that many rows before or after the current row. - value must be an integer expression not - containing any variables, aggregate functions, or window functions. - The value must not be null or negative; but it can be zero, which - selects the current row itself. + value FOLLOWING cases differ depending on + 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 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 + value must be an interval. In both modes, the value must not be null or + negative; but it can be zero, which just selects the current row itself. + + + + 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 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 a938a21334..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 -{ RANGE | ROWS } BETWEEN frame_start AND frame_end +{ 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 @@ -1817,6 +1817,13 @@ CURRENT ROW value FOLLOWING UNBOUNDED FOLLOWING + where the optional frame_exclusion_clause can be one of + +EXCLUDE CURRENT ROW +EXCLUDE GROUP +EXCLUDE TIES +EXCLUDE NO OTHERS + @@ -1857,8 +1864,8 @@ UNBOUNDED FOLLOWING 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. @@ -1871,7 +1878,7 @@ UNBOUNDED FOLLOWING - 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 @@ -1882,13 +1889,34 @@ UNBOUNDED FOLLOWING The value PRECEDING and - value FOLLOWING cases are currently only - allowed in ROWS mode. They indicate that the frame starts - or ends the specified number of rows before or after the current row. - value must be an integer expression not - containing any variables, aggregate functions, or window functions. - The value must not be null or negative; but it can be zero, which - just selects the current row. + value FOLLOWING cases, when used + in ROWS mode, indicate that the frame starts or ends the specified + number of rows before or after the current row. In ROWS mode, + value must be an integer expression not containing any variables, + aggregate functions, or window functions. + When used in RANGE mode, they indicate that the frame starts or ends when the value of + each row's ORDER BY column is within the start value and end value bounds. In RANGE mode, + value can be either an integer expression or a date/time interval. + 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 value must be an interval. In both modes, + the value must not be null or negative; but it can be zero, which just selects the current row. + + + + 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 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 02868749f6..58cf956594 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -37,6 +37,7 @@ #include "catalog/objectaccess.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_proc.h" +#include "catalog/pg_type.h" #include "executor/executor.h" #include "executor/nodeWindowAgg.h" #include "miscadmin.h" @@ -180,7 +181,11 @@ 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_frame(WindowAggState *winstate, int64 pos, +static bool row_is_in_group(WindowAggState *winstate, 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, TupleTableSlot *slot); static void update_frameheadpos(WindowObject winobj, TupleTableSlot *slot); static void update_frametailpos(WindowObject winobj, TupleTableSlot *slot); @@ -683,9 +688,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 @@ -737,15 +739,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, 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. + * 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) { @@ -766,6 +770,9 @@ eval_windowaggregates(WindowAggState *winstate) * - if we're processing the first row in the partition, or * - 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 * * Note that we don't strictly need to restart in the last case, but if @@ -780,6 +787,9 @@ eval_windowaggregates(WindowAggState *winstate) if (winstate->currentpos == 0 || (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) { peraggstate->restart = true; @@ -920,6 +930,8 @@ eval_windowaggregates(WindowAggState *winstate) */ for (;;) { + int ret; + /* Fetch next row if we didn't already */ if (TupIsNull(agg_row_slot)) { @@ -928,9 +940,15 @@ eval_windowaggregates(WindowAggState *winstate) break; /* must be end of partition */ } - /* Exit loop (for now) if not in frame */ - if (!row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot)) + /* + * Exit loop if no more rows can be in frame. Skip aggregation if current + * row is not in frame. + */ + ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot); + if (ret == -1) break; + else if (ret == 0) + goto next_tuple; /* Set tuple context for evaluation of aggregate arguments */ winstate->tmpcontext->ecxt_outertuple = agg_row_slot; @@ -951,6 +969,7 @@ eval_windowaggregates(WindowAggState *winstate) peraggstate); } + next_tuple: /* Reset per-input-tuple context after each tuple */ ResetExprContext(winstate->tmpcontext); @@ -1113,7 +1132,10 @@ begin_partition(WindowAggState *winstate) int readptr_flags = 0; /* If the frame head is potentially movable ... */ - if (!(winstate->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)) + 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 */ agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0); @@ -1155,6 +1177,94 @@ begin_partition(WindowAggState *winstate) */ tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot); winstate->spooled_rows++; + + /* + * In RANGE BETWEEN and GROUPS BETWEEN with values mode, 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. + * + * In GROUPS BETWEEN mode, we also store copies of these pointers and move them forward + * using the offset in order to keep track of the frame head/tail. + */ + if (winstate->frameOptions & (FRAMEOPTION_GROUPS_BETWEEN | FRAMEOPTION_RANGE_BETWEEN)) + { + int64 i, + partitionSize; + int64 *lenptr; + int64 groupLenSize = 16; + bool peers; + WindowObject currWinobj; + + if (winstate->numaggs > 0) + currWinobj = winstate->agg_winobj; + else + currWinobj = (&(winstate->perfunc[0]))->winobj; + winstate->winGroupsCount = 1; + winstate->winGroupLen = palloc0(sizeof(int64) * groupLenSize); + lenptr = winstate->winGroupLen; + *lenptr = 1; + partitionSize = WinGetPartitionRowCount(currWinobj); + + for (i = 0; i < partitionSize - 1; i++) + { + peers = WinRowsArePeers(currWinobj, i, i+1); + if (peers) + (*lenptr)++; + else + { + winstate->winGroupsCount++; + if (winstate->winGroupsCount > groupLenSize) + { + int64 prevSize = groupLenSize; + + groupLenSize *= 2; + winstate->winGroupLen = repalloc(winstate->winGroupLen, + sizeof(int64) * groupLenSize); + lenptr = winstate->winGroupLen; + lenptr += prevSize; + } + else + lenptr++; + + *lenptr = 1; + } + } + + /* Initialize frame head/tail pointers and lengths */ + winstate->frameheadWinGroup = winstate->winGroupLen; + winstate->currWinGroup = winstate->winGroupLen; + winstate->frametailWinGroup = winstate->winGroupLen; + winstate->frametailpos = 0; + winstate->prevRowTotal = 0; + winstate->frameheadGroupsCount = 0; + winstate->frametailGroupsCount = 0; + + /* + * In value following mode, move frame head/tail to offset position + */ + if (winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN && + (winstate->frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) == 0) + { + int64 offset = DatumGetInt64(winstate->startOffsetValue); + for (i = offset; i > 0; i--) + { + winstate->frameheadpos += *(winstate->frameheadWinGroup); + winstate->frameheadWinGroup++; + } + } + if (winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN && + (winstate->frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) == 0) + { + int64 offset = DatumGetInt64(winstate->endOffsetValue); + for (i = 0; i < offset; i++) + { + winstate->frametailpos += *(winstate->frametailWinGroup); + winstate->frametailWinGroup++; + winstate->frametailGroupsCount++; + } + } + } } /* @@ -1265,6 +1375,201 @@ release_partition(WindowAggState *winstate) tuplestore_end(winstate->buffer); winstate->buffer = NULL; winstate->partition_spooled = false; + + if (winstate->frameOptions & FRAMEOPTION_GROUPS_BETWEEN && + winstate->winGroupsCount > 0) + { + pfree(winstate->winGroupLen); + winstate->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(WindowAggState *winstate, int64 currpos, int64 slotpos, + int64 offset, bool preceding, bool end) +{ + int64 i, + len = 0, + currGroup = 0, + slotGroup = 0; + int64 *lenptr = winstate->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 < winstate->winGroupsCount; i++, lenptr++) + { + len += *lenptr; + if (len > currpos) + { + currGroup = i; + break; + } + } + lenptr = winstate->winGroupLen; + len = 0; + for (i = 0; i < winstate->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; +} + +/* + * row_is_in_range + * Determine whether a row is in range when in RANGE BETWEEN with values + * mode. + * + * Performs either an integer or a date/time comparison. + */ +static bool +row_is_in_range(Oid sortColOid, Datum currval, Datum slotval, Datum offset, + bool preceding, bool end) +{ + Oid intOid = INT8OID; + + /* If ORDER BY IS an int, perform int calculation */ + if (can_coerce_type(1, &sortColOid, &intOid, COERCION_IMPLICIT)) + { + int64 currint, + slotint, + offsetint; + currint = DatumGetInt64(currval); + slotint = DatumGetInt64(slotval); + offsetint = DatumGetInt64(offset); + + /* + * SQL:2011 defines the range offsets as unsigned, so reject values + * less than 0. + */ + if (offsetint < 0) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("RANGE offsets cannot be negative. invalid value %zd", offsetint))); + if (preceding) + offsetint = -offsetint; + + if (end) + { + if (slotint > currint + offsetint) + return false; + } + else + { + if (slotint < currint + offsetint) + return false; + } + } + else + { + /* ORDER BY is a date/time value, so perform interval calculation */ + Datum val; + PGFunction intervalFunc, + comparatorFunc; + + switch (sortColOid) + { + case TIMESTAMPOID: + if (preceding) + intervalFunc = timestamp_mi_interval; + else + intervalFunc = timestamp_pl_interval; + if (end) + comparatorFunc = timestamp_gt; + else + comparatorFunc = timestamp_lt; + break; + case TIMESTAMPTZOID: + if (preceding) + intervalFunc = timestamptz_mi_interval; + else + intervalFunc = timestamptz_pl_interval; + if (end) + comparatorFunc = timestamp_gt; + else + comparatorFunc = timestamp_lt; + break; + case INTERVALOID: + if (preceding) + intervalFunc = interval_mi; + else + intervalFunc = interval_pl; + if (end) + comparatorFunc = interval_gt; + else + comparatorFunc = interval_lt; + break; + case TIMETZOID: + if (preceding) + intervalFunc = timetz_mi_interval; + else + intervalFunc = timetz_pl_interval; + if (end) + comparatorFunc = timetz_gt; + else + comparatorFunc = timetz_lt; + break; + case DATEOID: + slotval = DirectFunctionCall1(date_timestamp, slotval); + if (preceding) + intervalFunc = date_mi_interval; + else + intervalFunc = date_pl_interval; + if (end) + comparatorFunc = timestamp_gt; + else + comparatorFunc = timestamp_lt; + break; + case TIMEOID: + currval = DirectFunctionCall1(time_interval, currval); + slotval = DirectFunctionCall1(time_interval, slotval); + if (preceding) + intervalFunc = interval_mi; + else + intervalFunc = interval_pl; + if (end) + comparatorFunc = interval_gt; + else + comparatorFunc = interval_lt; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("ORDER BY column in window function must be an integral or date/time"))); + } + val = DirectFunctionCall2(intervalFunc, currval, offset); + if (DirectFunctionCall2(comparatorFunc, slotval, val)) + return false; + } + + return true; } /* @@ -1275,12 +1580,16 @@ release_partition(WindowAggState *winstate) * The caller must have already determined that the row is in the partition * and fetched it into a slot. This function just encapsulates the framing * rules. + * + * Returns: + * -1, if the row is out of frame and no succeeding rows can be in frame + * 0, if the row is out of frame but succeeding rows might be in frame + * 1, if the row is in frame */ -static bool +static int row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot) { int frameOptions = winstate->frameOptions; - Assert(pos >= 0); /* else caller error */ /* First, check frame starting conditions */ @@ -1290,35 +1599,55 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot) { /* rows before current row are out of frame */ if (pos < winstate->currentpos) - return false; + 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 && !are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot)) - return false; + return -1; } else Assert(false); } else if (frameOptions & FRAMEOPTION_START_VALUE) { + int64 offset = DatumGetInt64(winstate->startOffsetValue); + if (frameOptions & FRAMEOPTION_ROWS) { - int64 offset = DatumGetInt64(winstate->startOffsetValue); - /* rows before current row + offset are out of frame */ if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) offset = -offset; - if (pos < winstate->currentpos + offset) - return false; + return 0; } else if (frameOptions & FRAMEOPTION_RANGE) { - /* parser should have rejected this */ - elog(ERROR, "window frame with value offset is not implemented"); + bool isnull, + preceding; + Datum slotval, + currval; + + slotval = slot_getattr(slot, 1, &isnull); + if (isnull) + return 0; + currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull); + if (isnull) + return 0; + preceding = (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) != 0 ? true : false; + if (!row_is_in_range(winstate->firstSortColOid, currval, + 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, winstate->currentpos, pos, offset, preceding, false)) + return 0; } else Assert(false); @@ -1331,42 +1660,78 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot) { /* rows after current row are out of frame */ if (pos > winstate->currentpos) - return false; + 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 && !are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot)) - return false; + return -1; } else Assert(false); } else if (frameOptions & FRAMEOPTION_END_VALUE) { + int64 offset = DatumGetInt64(winstate->endOffsetValue); if (frameOptions & FRAMEOPTION_ROWS) { - int64 offset = DatumGetInt64(winstate->endOffsetValue); - /* rows after current row + offset are out of frame */ if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) offset = -offset; - if (pos > winstate->currentpos + offset) - return false; + return -1; } else if (frameOptions & FRAMEOPTION_RANGE) { - /* parser should have rejected this */ - elog(ERROR, "window frame with value offset is not implemented"); + bool isnull, + preceding; + Datum slotval, + currval; + + slotval = slot_getattr(slot, 1, &isnull); + if (isnull) + return -1; + currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull); + if (isnull) + return -1; + preceding = (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) != 0 ? true : false; + if (!row_is_in_range(winstate->firstSortColOid, currval, + 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, winstate->currentpos, pos, offset, preceding, true)) + return -1; } else Assert(false); } + /* Check exclusion clause */ + if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT) + { + if (pos == winstate->currentpos) + return 0; + } 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)) + return 0; + } + /* If we get here, it's in frame */ - return true; + return 1; } /* @@ -1402,7 +1767,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; @@ -1441,11 +1806,11 @@ update_frameheadpos(WindowObject winobj, TupleTableSlot *slot) } else if (frameOptions & FRAMEOPTION_START_VALUE) { + int64 offset = DatumGetInt64(winstate->startOffsetValue); + if (frameOptions & FRAMEOPTION_ROWS) { /* In ROWS mode, bound is physically n before/after current */ - int64 offset = DatumGetInt64(winstate->startOffsetValue); - if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) offset = -offset; @@ -1464,8 +1829,39 @@ update_frameheadpos(WindowObject winobj, TupleTableSlot *slot) } else if (frameOptions & FRAMEOPTION_RANGE) { - /* parser should have rejected this */ - elog(ERROR, "window frame with value offset is not implemented"); + bool isnull; + Datum slotval, + currval; + window_gettupleslot(winobj, winstate->frameheadpos, slot); + while (winstate->frameheadGroupsCount < winstate->winGroupsCount - 1) + { + slotval = slot_getattr(slot, 1, &isnull); + if (isnull) + break; + currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull); + if (isnull) + break; + if (row_is_in_range(winstate->firstSortColOid, + currval, slotval, winstate->startOffsetValue, + (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) ? true : false, + false)) + break; + winstate->frameheadpos += *(winstate->frameheadWinGroup); + winstate->frameheadWinGroup++; + winstate->frameheadGroupsCount++; + window_gettupleslot(winobj, winstate->frameheadpos, slot); + } + winstate->framehead_valid = true; + } + else if (frameOptions & FRAMEOPTION_GROUPS) + { + if (!row_is_in_group(winstate, winstate->currentpos, winstate->frameheadpos, offset, + (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) ? true : false, false)) + { + winstate->frameheadpos += *(winstate->frameheadWinGroup); + winstate->frameheadWinGroup++; + } + winstate->framehead_valid = true; } else Assert(false); @@ -1508,7 +1904,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; @@ -1545,11 +1941,11 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot) } else if (frameOptions & FRAMEOPTION_END_VALUE) { + int64 offset = DatumGetInt64(winstate->endOffsetValue); + if (frameOptions & FRAMEOPTION_ROWS) { /* In ROWS mode, bound is physically n before/after current */ - int64 offset = DatumGetInt64(winstate->endOffsetValue); - if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) offset = -offset; @@ -1568,8 +1964,56 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot) } else if (frameOptions & FRAMEOPTION_RANGE) { - /* parser should have rejected this */ - elog(ERROR, "window frame with value offset is not implemented"); + bool isnull; + Datum slotval, + currval; + + if (winstate->frametailpos < 0) + winstate->frametailpos = 0; + if (winstate->frametailpos > 0) + winstate->frametailpos -= *(winstate->frametailWinGroup) - 1; + while (winstate->frametailGroupsCount < winstate->winGroupsCount - 1) + { + window_gettupleslot(winobj, winstate->frametailpos + *(winstate->frametailWinGroup), slot); + slotval = slot_getattr(slot, 1, &isnull); + if (isnull) + break; + currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull); + if (isnull) + break; + if (!row_is_in_range(winstate->firstSortColOid, + currval, slotval, winstate->endOffsetValue, + (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) ? true : false, + true)) + break; + winstate->frametailpos += *(winstate->frametailWinGroup); + winstate->frametailWinGroup++; + winstate->frametailGroupsCount++; + window_gettupleslot(winobj, winstate->frametailpos, slot); + } + + winstate->frametailpos += *(winstate->frametailWinGroup) - 1; + winstate->frametail_valid = true; + } + else if (frameOptions & FRAMEOPTION_GROUPS) + { + if (winstate->frametailpos < 0) + winstate->frametailpos = 0; + + if (winstate->frametailpos > 0) + winstate->frametailpos -= *(winstate->frametailWinGroup) - 1; + if (winstate->prevRowTotal + *(winstate->currWinGroup) <= winstate->currentpos && + winstate->frametailGroupsCount < winstate->winGroupsCount - 1) + { + winstate->frametailpos += *(winstate->frametailWinGroup); + winstate->frametailWinGroup++; + winstate->frametailGroupsCount++; + winstate->prevRowTotal += *(winstate->currWinGroup); + winstate->currWinGroup++; + } + + winstate->frametailpos += *(winstate->frametailWinGroup) - 1; + winstate->frametail_valid = true; } else Assert(false); @@ -1627,7 +2071,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); @@ -1652,7 +2096,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); @@ -1670,6 +2114,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 @@ -1989,6 +2434,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) agg_winobj->readptr = -1; winstate->agg_winobj = agg_winobj; } + winstate->firstSortColOid = node->firstSortColOid; /* copy frame options to state node for easy access */ winstate->frameOptions = node->frameOptions; @@ -2736,7 +3182,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, WindowAggState *winstate; ExprContext *econtext; TupleTableSlot *slot; - bool gottuple; + int gottuple; int64 abs_pos; Assert(WindowObjectIsValid(winobj)); @@ -2767,7 +3213,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, if (gottuple) gottuple = row_is_in_frame(winstate, abs_pos, slot); - if (!gottuple) + if (gottuple != 1) { if (isout) *isout = true; @@ -2794,7 +3240,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, * was its mark. Perhaps use a separate mark for frame head * probes? */ - if ((frameOptions & FRAMEOPTION_RANGE) && + if ((frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) && !(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)) { update_frameheadpos(winobj, winstate->temp_slot_2); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 84d717102d..d37e6d6bc9 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1008,6 +1008,7 @@ _copyWindowAgg(const WindowAgg *from) COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber)); COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid)); } + COPY_SCALAR_FIELD(firstSortColOid); COPY_SCALAR_FIELD(frameOptions); COPY_NODE_FIELD(startOffset); COPY_NODE_FIELD(endOffset); @@ -2407,6 +2408,7 @@ _copyWindowClause(const WindowClause *from) COPY_STRING_FIELD(refname); COPY_NODE_FIELD(partitionClause); COPY_NODE_FIELD(orderClause); + COPY_SCALAR_FIELD(firstSortColOid); COPY_SCALAR_FIELD(frameOptions); COPY_NODE_FIELD(startOffset); COPY_NODE_FIELD(endOffset); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 2e869a9d5d..4a16d3873c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2730,6 +2730,7 @@ _equalWindowClause(const WindowClause *a, const WindowClause *b) COMPARE_STRING_FIELD(refname); COMPARE_NODE_FIELD(partitionClause); COMPARE_NODE_FIELD(orderClause); + COMPARE_SCALAR_FIELD(firstSortColOid); COMPARE_SCALAR_FIELD(frameOptions); COMPARE_NODE_FIELD(startOffset); COMPARE_NODE_FIELD(endOffset); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e468d7cc41..caf75a499a 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -836,6 +836,7 @@ _outWindowAgg(StringInfo str, const WindowAgg *node) for (i = 0; i < node->ordNumCols; i++) appendStringInfo(str, " %u", node->ordOperators[i]); + WRITE_INT_FIELD(firstSortColOid); WRITE_INT_FIELD(frameOptions); WRITE_NODE_FIELD(startOffset); WRITE_NODE_FIELD(endOffset); @@ -2977,6 +2978,7 @@ _outWindowClause(StringInfo str, const WindowClause *node) WRITE_STRING_FIELD(refname); WRITE_NODE_FIELD(partitionClause); WRITE_NODE_FIELD(orderClause); + WRITE_INT_FIELD(firstSortColOid); WRITE_INT_FIELD(frameOptions); WRITE_NODE_FIELD(startOffset); WRITE_NODE_FIELD(endOffset); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 1133c70a1c..29ce310115 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -366,6 +366,7 @@ _readWindowClause(void) READ_STRING_FIELD(refname); READ_NODE_FIELD(partitionClause); READ_NODE_FIELD(orderClause); + READ_INT_FIELD(firstSortColOid); READ_INT_FIELD(frameOptions); READ_NODE_FIELD(startOffset); READ_NODE_FIELD(endOffset); @@ -2135,6 +2136,7 @@ _readWindowAgg(void) READ_INT_FIELD(ordNumCols); READ_ATTRNUMBER_ARRAY(ordColIdx, local_node->ordNumCols); READ_OID_ARRAY(ordOperators, local_node->ordNumCols); + READ_INT_FIELD(firstSortColOid); READ_INT_FIELD(frameOptions); READ_NODE_FIELD(startOffset); READ_NODE_FIELD(endOffset); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1a0d3a885f..db5d265374 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -260,8 +260,8 @@ static Material *make_material(Plan *lefttree); static WindowAgg *make_windowagg(List *tlist, Index winref, int partNumCols, AttrNumber *partColIdx, Oid *partOperators, int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, - int frameOptions, Node *startOffset, Node *endOffset, - Plan *lefttree); + Oid firstSortColOid, int frameOptions, Node *startOffset, + Node *endOffset, Plan *lefttree); static Group *make_group(List *tlist, List *qual, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Plan *lefttree); @@ -2116,6 +2116,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path) ordNumCols, ordColIdx, ordOperators, + wc->firstSortColOid, wc->frameOptions, wc->startOffset, wc->endOffset, @@ -6074,8 +6075,8 @@ static WindowAgg * make_windowagg(List *tlist, Index winref, int partNumCols, AttrNumber *partColIdx, Oid *partOperators, int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, - int frameOptions, Node *startOffset, Node *endOffset, - Plan *lefttree) + Oid firstSortColOid, int frameOptions, Node *startOffset, + Node *endOffset, Plan *lefttree) { WindowAgg *node = makeNode(WindowAgg); Plan *plan = &node->plan; @@ -6087,6 +6088,7 @@ make_windowagg(List *tlist, Index winref, node->ordNumCols = ordNumCols; node->ordColIdx = ordColIdx; node->ordOperators = ordOperators; + node->firstSortColOid = firstSortColOid; node->frameOptions = frameOptions; node->startOffset = startOffset; node->endOffset = endOffset; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ebfc94f896..917341f1c5 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -569,7 +569,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type filter_clause %type window_clause window_definition_list opt_partition_clause %type window_definition over_clause window_specification - opt_frame_clause frame_extent frame_bound + opt_frame_clause frame_extent frame_bound opt_window_exclusion_clause %type opt_existing_window_name %type opt_if_not_exists %type generated_when override_kind @@ -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 @@ -656,7 +656,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); NULLS_P NUMERIC OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR - ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER + ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY @@ -676,7 +676,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); SUBSCRIPTION SUBSTRING SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN - TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P + TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED @@ -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,33 +14001,43 @@ 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:2008 frame_clause grammar. - * We don't support yet. */ opt_frame_clause: - RANGE frame_extent + RANGE frame_extent opt_window_exclusion_clause { WindowDef *n = $2; + WindowDef *n2 = $3; n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE; - if (n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING | - FRAMEOPTION_END_VALUE_PRECEDING)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE PRECEDING is only supported with UNBOUNDED"), - parser_errposition(@1))); - if (n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING | - FRAMEOPTION_END_VALUE_FOLLOWING)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("RANGE FOLLOWING is only supported with UNBOUNDED"), - parser_errposition(@1))); + if ((n->frameOptions & (FRAMEOPTION_START_VALUE_PRECEDING | + FRAMEOPTION_END_VALUE_PRECEDING)) || + (n->frameOptions & (FRAMEOPTION_START_VALUE_FOLLOWING | + FRAMEOPTION_END_VALUE_FOLLOWING))) + n->frameOptions |= FRAMEOPTION_RANGE_BETWEEN; + if (n2 != NULL) + n->frameOptions |= n2->frameOptions; $$ = n; } - | ROWS frame_extent + | ROWS frame_extent opt_window_exclusion_clause { WindowDef *n = $2; + WindowDef *n2 = $3; n->frameOptions |= FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS; + if (n2 != NULL) + 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*/ @@ -14144,6 +14154,34 @@ frame_bound: } ; +opt_window_exclusion_clause: + EXCLUDE CURRENT_P ROW + { + WindowDef *n = makeNode(WindowDef); + 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); + n->frameOptions = FRAMEOPTION_EXCLUDE_TIES; + $$ = n; + } + | EXCLUDE NO OTHERS + { + WindowDef *n = makeNode(WindowDef); + n->frameOptions = FRAMEOPTION_EXCLUDE_NO_OTHERS; + $$ = n; + } + | /*EMPTY*/ { $$ = NULL; } + ; + /* * Supporting nonterminals for expressions. @@ -15005,6 +15043,7 @@ unreserved_keyword: | GENERATED | GLOBAL | GRANTED + | GROUPS | HANDLER | HEADER_P | HOLD @@ -15070,6 +15109,7 @@ unreserved_keyword: | OPTION | OPTIONS | ORDINALITY + | OTHERS | OVER | OVERRIDING | OWNED @@ -15160,6 +15200,7 @@ unreserved_keyword: | TEMPLATE | TEMPORARY | TEXT_P + | TIES | TRANSACTION | TRANSFORM | TRIGGER 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 2828bbf796..7d92a8edfd 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -96,6 +96,8 @@ static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle, static WindowClause *findWindowClause(List *wclist, const char *name); static Node *transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause); +static inline void checkRangeBetweenOids(ParseState *pstate, WindowDef *windef, + Oid sortOid, Node *offset); /* @@ -2608,6 +2610,35 @@ transformSortClause(ParseState *pstate, return sortlist; } +static inline void +checkRangeBetweenOids(ParseState *pstate, + WindowDef *windef, + Oid sortOid, + Node *offset) +{ + Oid offsetOid = exprType(offset); + Oid intOid = INT8OID; + Oid intervalOid = INTERVALOID; + + if (can_coerce_type(1, &sortOid, &intOid, COERCION_IMPLICIT)) + { + if (!can_coerce_type(1, &offsetOid, &intOid, COERCION_IMPLICIT) || + !can_coerce_type(1, &offsetOid, &intOid, COERCION_IMPLICIT)) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("Offsets must be an integral"), + parser_errposition(pstate, windef->location))); + } + else if (!can_coerce_type(1, &offsetOid, &intervalOid, COERCION_IMPLICIT) || + !can_coerce_type(1, &offsetOid, &intervalOid, COERCION_IMPLICIT)) + { + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("Offsets must be an interval"), + parser_errposition(pstate, windef->location))); + } +} + /* * transformWindowDefinitions - * transform window definitions (WindowDef to WindowClause) @@ -2753,6 +2784,7 @@ transformWindowDefinitions(ParseState *pstate, parser_errposition(pstate, windef->location))); } wc->frameOptions = windef->frameOptions; + /* Process frame offset expressions */ wc->startOffset = transformFrameOffset(pstate, wc->frameOptions, windef->startOffset); @@ -2760,9 +2792,36 @@ transformWindowDefinitions(ParseState *pstate, windef->endOffset); wc->winref = winref; + /* The RANGE BETWEEN clause requires exactly one ORDER BY column, + * of either an int or date/time type and start/end values to match by type. + */ + if (wc->frameOptions & FRAMEOPTION_RANGE_BETWEEN) + { + SortBy *sortby; + TargetEntry *tle; + Oid sortOid; + + if (list_length(orderClause) != 1) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("RANGE clause requires exactly one ORDER BY column"), + parser_errposition(pstate, windef->location))); + sortby = (SortBy *) lfirst(list_head(windef->orderClause)); + tle = findTargetlistEntrySQL99(pstate, sortby->node, + targetlist, EXPR_KIND_WINDOW_ORDER); + + /* Check that the sortOid and start/end offset Oids match */ + sortOid = exprType((Node *) tle->expr); + if (wc->frameOptions & FRAMEOPTION_START_VALUE) + checkRangeBetweenOids(pstate, windef, sortOid, wc->startOffset); + if (wc->frameOptions & FRAMEOPTION_END_VALUE) + checkRangeBetweenOids(pstate, windef, sortOid, wc->endOffset); + + wc->firstSortColOid = sortOid; + } + result = lappend(result, wc); } - return result; } @@ -3513,16 +3572,32 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause) } else if (frameOptions & FRAMEOPTION_RANGE) { + Oid nodeOid; + Oid intervalOid = INTERVALOID; + /* Transform the raw expression tree */ node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE); /* - * this needs a lot of thought to decide how to support in the context - * of Postgres' extensible datatype framework + * Check that the specified type is an interval or integral */ constructName = "RANGE"; - /* error was already thrown by gram.y, this is just a backstop */ - elog(ERROR, "window frame with value offset is not implemented"); + nodeOid = exprType(node); + if (can_coerce_type(1, &nodeOid, &intervalOid, COERCION_IMPLICIT)) + node = coerce_to_specific_type(pstate, node, INTERVALOID, constructName); + 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 { 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 e6b085637b..3610d62b67 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2227,6 +2227,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 8514c21c40..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) @@ -5914,6 +5916,14 @@ get_rule_windowspec(WindowClause *wc, List *targetList, else Assert(false); } + 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) + appendStringInfoString(buf, "EXCLUDE NO OTHERS "); /* we will now have a trailing space; remove it */ buf->len--; } diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 44d8c47d2c..acce1d2f49 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1889,6 +1889,7 @@ typedef struct WindowAggState int64 aggregatedbase; /* start row for current aggregates */ int64 aggregatedupto; /* rows before this one are aggregated */ + Oid firstSortColOid; /* First ORDER BY Oid, used for RANGE with values */ int frameOptions; /* frame_clause options, see WindowDef */ ExprState *startOffset; /* expression for starting bound offset */ ExprState *endOffset; /* expression for ending bound offset */ @@ -1918,6 +1919,16 @@ typedef struct WindowAggState TupleTableSlot *agg_row_slot; TupleTableSlot *temp_slot_1; TupleTableSlot *temp_slot_2; + + /* used for RANGE BETWEEN and GROUPS BETWEEN with values */ + int64 winGroupsCount; /* number of window groups */ + int64 *winGroupLen; /* length of each window group */ + int64 *currWinGroup; /* current window group */ + int64 prevRowTotal; /* previous row total */ + int64 *frameheadWinGroup; /* window group for the frame head */ + int64 *frametailWinGroup; /* window group for the frame tail */ + int64 frameheadGroupsCount; /* count of frame head groups */ + int64 frametailGroupsCount; /* count of frame tail groups */ } WindowAggState; /* ---------------- diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2eaa6b2774..626c1864a9 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -499,22 +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_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) @@ -525,6 +531,10 @@ typedef struct WindowDef (FRAMEOPTION_RANGE | FRAMEOPTION_START_UNBOUNDED_PRECEDING | \ FRAMEOPTION_END_CURRENT_ROW) +#define FRAMEOPTION_EXCLUSION \ + (FRAMEOPTION_EXCLUDE_CURRENT | FRAMEOPTION_EXCLUDE_TIES | \ + FRAMEOPTION_EXCLUDE_GROUP) + /* * RangeSubselect - subquery appearing in a FROM clause */ @@ -1275,6 +1285,7 @@ typedef struct GroupingSet * if the clause originally came from WINDOW, and is NULL if it originally * was an OVER clause (but note that we collapse out duplicate OVERs). * partitionClause and orderClause are lists of SortGroupClause structs. + * firstSortColOid is set if the clause is a RANGE BETWEEN with values. * winref is an ID number referenced by WindowFunc nodes; it must be unique * among the members of a Query's windowClause list. * When refname isn't null, the partitionClause is always copied from there; @@ -1288,6 +1299,7 @@ typedef struct WindowClause char *refname; /* referenced window name, if any */ List *partitionClause; /* PARTITION BY list */ List *orderClause; /* ORDER BY list */ + Oid firstSortColOid; /* First ORDER BY Oid, used for RANGE with values */ int frameOptions; /* frame_clause options, see WindowDef */ Node *startOffset; /* expression for starting bound, if any */ Node *endOffset; /* expression for ending bound, if any */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index d763da647b..13e4bede7f 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -807,6 +807,7 @@ typedef struct WindowAgg int ordNumCols; /* number of columns in ordering clause */ AttrNumber *ordColIdx; /* their indexes in the target list */ Oid *ordOperators; /* equality operators for ordering columns */ + Oid firstSortColOid; /* First ORDER BY Oid, used for RANGE with values */ int frameOptions; /* frame_clause options, see WindowDef */ Node *startOffset; /* expression for starting bound, if any */ Node *endOffset; /* expression for ending bound, if any */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index a932400058..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) @@ -283,6 +284,7 @@ PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD) PG_KEYWORD("or", OR, RESERVED_KEYWORD) PG_KEYWORD("order", ORDER, RESERVED_KEYWORD) PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD) +PG_KEYWORD("others", OTHERS, UNRESERVED_KEYWORD) PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD) PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD) @@ -397,6 +399,7 @@ PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD) PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD) PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD) PG_KEYWORD("then", THEN, RESERVED_KEYWORD) +PG_KEYWORD("ties", TIES, UNRESERVED_KEYWORD) PG_KEYWORD("time", TIME, COL_NAME_KEYWORD) PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD) PG_KEYWORD("to", TO, RESERVED_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 19f909f3d1..5090147a72 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -5,19 +5,24 @@ CREATE TEMPORARY TABLE empsalary ( depname varchar, empno bigint, salary int, - enroll_date date + enroll_date date, + enroll_time time, + enroll_timetz timetz, + enroll_interval interval, + enroll_timestamptz timestamptz, + enroll_timestamp timestamp ); INSERT INTO empsalary VALUES -('develop', 10, 5200, '2007-08-01'), -('sales', 1, 5000, '2006-10-01'), -('personnel', 5, 3500, '2007-12-10'), -('sales', 4, 4800, '2007-08-08'), -('personnel', 2, 3900, '2006-12-23'), -('develop', 7, 4200, '2008-01-01'), -('develop', 9, 4500, '2008-01-01'), -('sales', 3, 4800, '2007-08-01'), -('develop', 8, 6000, '2006-10-01'), -('develop', 11, 5200, '2007-08-15'); +('develop', 10, 5200, '2007-08-01', '11:00', '11:00 BST', '1 year'::interval, TIMESTAMP '2000-10-19 10:23:54+01', TIMESTAMP '2000-10-19 10:23:54'), +('sales', 1, 5000, '2006-10-01', '12:00', '12:00 BST', '2 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'), +('personnel', 5, 3500, '2007-12-10', '13:00', '13:00 BST', '3 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'), +('sales', 4, 4800, '2007-08-08', '14:00', '14:00 BST', '4 years'::interval, TIMESTAMP '2002-10-19 10:23:54+01', TIMESTAMP '2002-10-19 10:23:54'), +('personnel', 2, 3900, '2006-12-23', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2003-10-19 10:23:54+01', TIMESTAMP '2003-10-19 10:23:54'), +('develop', 7, 4200, '2008-01-01', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2004-10-19 10:23:54+01', TIMESTAMP '2004-10-19 10:23:54'), +('develop', 9, 4500, '2008-01-01', '17:00', '17:00 BST', '7 years'::interval, TIMESTAMP '2005-10-19 10:23:54+01', TIMESTAMP '2005-10-19 10:23:54'), +('sales', 3, 4800, '2007-08-01', '18:00', '18:00 BST', '8 years'::interval, TIMESTAMP '2006-10-19 10:23:54+01', TIMESTAMP '2006-10-19 10:23:54'), +('develop', 8, 6000, '2006-10-01', '19:00', '19:00 BST', '9 years'::interval, TIMESTAMP '2007-10-19 10:23:54+01', TIMESTAMP '2007-10-19 10:23:54'), +('develop', 11, 5200, '2007-08-15', '20:00', '20:00 BST', '10 years'::interval, TIMESTAMP '2008-10-19 10:23:54+01', TIMESTAMP '2008-10-19 10:23:54'); SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; depname | empno | salary | sum -----------+-------+--------+------- @@ -819,6 +824,74 @@ FROM tenk1 WHERE unique1 < 10; 10 | 0 | 0 (10 rows) +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude no others), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + 7 | 4 | 0 + 13 | 2 | 2 + 22 | 1 | 1 + 26 | 6 | 2 + 29 | 9 | 1 + 31 | 8 | 0 + 32 | 5 | 1 + 23 | 3 | 3 + 15 | 7 | 3 + 10 | 0 | 0 +(10 rows) + +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + 3 | 4 | 0 + 11 | 2 | 2 + 21 | 1 | 1 + 20 | 6 | 2 + 20 | 9 | 1 + 23 | 8 | 0 + 27 | 5 | 1 + 20 | 3 | 3 + 8 | 7 | 3 + 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; + sum | unique1 | four +-----+---------+------ + 4 | 4 | 0 + 2 | 2 | 2 + 1 | 1 | 1 + 6 | 6 | 2 + 9 | 9 | 1 + 8 | 8 | 0 + 5 | 5 | 1 + 3 | 3 | 3 + 7 | 7 | 3 + 0 | 0 | 0 +(10 rows) + SELECT sum(unique1) over (rows between 2 preceding and 1 preceding), unique1, four FROM tenk1 WHERE unique1 < 10; @@ -887,13 +960,57 @@ FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four); 10 | 7 | 3 (10 rows) --- fail: not implemented yet -SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding), +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude current row), unique1, four -FROM tenk1 WHERE unique1 < 10; -ERROR: RANGE PRECEDING is only supported with UNBOUNDED -LINE 1: SELECT sum(unique1) over (order by four range between 2::int... - ^ +FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four); + sum | unique1 | four +-----+---------+------ + 12 | 0 | 0 + 4 | 8 | 0 + 8 | 4 | 0 + 22 | 5 | 1 + 18 | 9 | 1 + 26 | 1 | 1 + 29 | 6 | 2 + 33 | 2 | 2 + 42 | 3 | 3 + 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); + sum | unique1 | four +-----+---------+------ + 0 | 0 | 0 + 8 | 8 | 0 + 4 | 4 | 0 + 17 | 5 | 1 + 21 | 9 | 1 + 13 | 1 | 1 + 33 | 6 | 2 + 29 | 2 | 2 + 38 | 3 | 3 + 42 | 7 | 3 +(10 rows) + SELECT first_value(unique1) over w, nth_value(unique1, 2) over w AS nth_2, last_value(unique1) over w, unique1, four @@ -958,6 +1075,1433 @@ 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 rows between 1 preceding and 1 following + exclude current row) 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 CURRENT ROW) 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 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 +----+---------- + 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 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE TIES) 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 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 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE NO OTHERS) 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 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 +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 no others), + 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 current row), + 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 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; + 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 6::int2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + 33 | 0 | 0 + 41 | 8 | 0 + 37 | 4 | 0 + 35 | 5 | 1 + 39 | 9 | 1 + 31 | 1 | 1 + 43 | 6 | 2 + 39 | 2 | 2 + 26 | 3 | 3 + 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; + sum | unique1 | four +-----+---------+------ + 4 | 0 | 0 + 12 | 4 | 0 + 12 | 8 | 0 + 6 | 1 | 1 + 15 | 5 | 1 + 14 | 9 | 1 + 8 | 2 | 2 + 8 | 6 | 2 + 10 | 3 | 3 + 10 | 7 | 3 +(10 rows) + +SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following + exclude current row),unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + 4 | 0 | 0 + 8 | 4 | 0 + 4 | 8 | 0 + 5 | 1 | 1 + 10 | 5 | 1 + 5 | 9 | 1 + 6 | 2 | 2 + 2 | 6 | 2 + 7 | 3 | 3 + 3 | 7 | 3 +(10 rows) + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following), + salary, enroll_date from empsalary; + sum | salary | enroll_date +-------+--------+------------- + 34900 | 5000 | 10-01-2006 + 34900 | 6000 | 10-01-2006 + 38400 | 3900 | 12-23-2006 + 47100 | 4800 | 08-01-2007 + 47100 | 5200 | 08-01-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 5200 | 08-15-2007 + 36100 | 3500 | 12-10-2007 + 32200 | 4500 | 01-01-2008 + 32200 | 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 current row), salary, enroll_date from empsalary; + sum | salary | enroll_date +-------+--------+------------- + 29900 | 5000 | 10-01-2006 + 28900 | 6000 | 10-01-2006 + 34500 | 3900 | 12-23-2006 + 42300 | 4800 | 08-01-2007 + 41900 | 5200 | 08-01-2007 + 42300 | 4800 | 08-08-2007 + 41900 | 5200 | 08-15-2007 + 32600 | 3500 | 12-10-2007 + 27700 | 4500 | 01-01-2008 + 28000 | 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 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 +-------+--------+------------- + 28900 | 5000 | 10-01-2006 + 29900 | 6000 | 10-01-2006 + 38400 | 3900 | 12-23-2006 + 41900 | 4800 | 08-01-2007 + 42300 | 5200 | 08-01-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 5200 | 08-15-2007 + 36100 | 3500 | 12-10-2007 + 28000 | 4500 | 01-01-2008 + 27700 | 4200 | 01-01-2008 +(10 rows) + +select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following), + salary, enroll_time from empsalary; + sum | salary | enroll_time +-------+--------+------------- + 13700 | 5200 | 11:00:00 + 18500 | 5000 | 12:00:00 + 21400 | 3500 | 13:00:00 + 16400 | 4800 | 14:00:00 + 17400 | 3900 | 15:00:00 + 17400 | 4200 | 15:00:00 + 15300 | 4500 | 17:00:00 + 20500 | 4800 | 18:00:00 + 16000 | 6000 | 19:00:00 + 11200 | 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 current row), 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 + 13500 | 3900 | 15:00:00 + 13200 | 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 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 +-------+--------+------------- + 13700 | 5200 | 11:00:00 + 18500 | 5000 | 12:00:00 + 21400 | 3500 | 13:00:00 + 16400 | 4800 | 14:00:00 + 13200 | 3900 | 15:00:00 + 13500 | 4200 | 15:00:00 + 15300 | 4500 | 17:00:00 + 20500 | 4800 | 18:00:00 + 16000 | 6000 | 19:00:00 + 11200 | 5200 | 20:00:00 +(10 rows) + +select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following), + salary, enroll_timetz from empsalary; + sum | salary | enroll_timetz +-------+--------+--------------- + 13700 | 5200 | 11:00:00+01 + 18500 | 5000 | 12:00:00+01 + 21400 | 3500 | 13:00:00+01 + 16400 | 4800 | 14:00:00+01 + 17400 | 3900 | 15:00:00+01 + 17400 | 4200 | 15:00:00+01 + 15300 | 4500 | 17:00:00+01 + 20500 | 4800 | 18:00:00+01 + 16000 | 6000 | 19:00:00+01 + 11200 | 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 current row), 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 + 13500 | 3900 | 15:00:00+01 + 13200 | 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 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 +-------+--------+--------------- + 13700 | 5200 | 11:00:00+01 + 18500 | 5000 | 12:00:00+01 + 21400 | 3500 | 13:00:00+01 + 16400 | 4800 | 14:00:00+01 + 13200 | 3900 | 15:00:00+01 + 13500 | 4200 | 15:00:00+01 + 15300 | 4500 | 17:00:00+01 + 20500 | 4800 | 18:00:00+01 + 16000 | 6000 | 19:00:00+01 + 11200 | 5200 | 20:00:00+01 +(10 rows) + +select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_interval from empsalary; + sum | salary | enroll_interval +-------+--------+----------------- + 13700 | 5200 | @ 1 year + 18500 | 5000 | @ 2 years + 21400 | 3500 | @ 3 years + 16400 | 4800 | @ 4 years + 17400 | 3900 | @ 5 years + 17400 | 4200 | @ 5 years + 15300 | 4500 | @ 7 years + 20500 | 4800 | @ 8 years + 16000 | 6000 | @ 9 years + 11200 | 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 current row), 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 + 13500 | 3900 | @ 5 years + 13200 | 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 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 +-------+--------+----------------- + 13700 | 5200 | @ 1 year + 18500 | 5000 | @ 2 years + 21400 | 3500 | @ 3 years + 16400 | 4800 | @ 4 years + 13200 | 3900 | @ 5 years + 13500 | 4200 | @ 5 years + 15300 | 4500 | @ 7 years + 20500 | 4800 | @ 8 years + 16000 | 6000 | @ 9 years + 11200 | 5200 | @ 10 years +(10 rows) + +select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_timestamptz from empsalary; + sum | salary | enroll_timestamptz +-------+--------+------------------------------ + 18500 | 5200 | Thu Oct 19 10:23:54 2000 PDT + 22400 | 5000 | Fri Oct 19 10:23:54 2001 PDT + 22400 | 3500 | Fri Oct 19 10:23:54 2001 PDT + 21400 | 4800 | Sat Oct 19 10:23:54 2002 PDT + 17400 | 3900 | Sun Oct 19 10:23:54 2003 PDT + 17400 | 4200 | Tue Oct 19 10:23:54 2004 PDT + 19500 | 4500 | Wed Oct 19 10:23:54 2005 PDT + 20500 | 4800 | Thu Oct 19 10:23:54 2006 PDT + 16000 | 6000 | Fri Oct 19 10:23:54 2007 PDT + 11200 | 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 current row), salary, enroll_timestamptz from empsalary; + sum | salary | enroll_timestamptz +-------+--------+------------------------------ + 13300 | 5200 | Thu Oct 19 10:23:54 2000 PDT + 17400 | 5000 | Fri Oct 19 10:23:54 2001 PDT + 18900 | 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 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 +-------+--------+------------------------------ + 18500 | 5200 | Thu Oct 19 10:23:54 2000 PDT + 18900 | 5000 | Fri Oct 19 10:23:54 2001 PDT + 17400 | 3500 | Fri Oct 19 10:23:54 2001 PDT + 21400 | 4800 | Sat Oct 19 10:23:54 2002 PDT + 17400 | 3900 | Sun Oct 19 10:23:54 2003 PDT + 17400 | 4200 | Tue Oct 19 10:23:54 2004 PDT + 19500 | 4500 | Wed Oct 19 10:23:54 2005 PDT + 20500 | 4800 | Thu Oct 19 10:23:54 2006 PDT + 16000 | 6000 | Fri Oct 19 10:23:54 2007 PDT + 11200 | 5200 | Sun Oct 19 10:23:54 2008 PDT +(10 rows) + +select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_timestamp from empsalary; + sum | salary | enroll_timestamp +-------+--------+-------------------------- + 18500 | 5200 | Thu Oct 19 10:23:54 2000 + 22400 | 5000 | Fri Oct 19 10:23:54 2001 + 22400 | 3500 | Fri Oct 19 10:23:54 2001 + 21400 | 4800 | Sat Oct 19 10:23:54 2002 + 17400 | 3900 | Sun Oct 19 10:23:54 2003 + 17400 | 4200 | Tue Oct 19 10:23:54 2004 + 19500 | 4500 | Wed Oct 19 10:23:54 2005 + 20500 | 4800 | Thu Oct 19 10:23:54 2006 + 16000 | 6000 | Fri Oct 19 10:23:54 2007 + 11200 | 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 current row), salary, enroll_timestamp from empsalary; + sum | salary | enroll_timestamp +-------+--------+-------------------------- + 13300 | 5200 | Thu Oct 19 10:23:54 2000 + 17400 | 5000 | Fri Oct 19 10:23:54 2001 + 18900 | 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 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 +-------+--------+-------------------------- + 18500 | 5200 | Thu Oct 19 10:23:54 2000 + 18900 | 5000 | Fri Oct 19 10:23:54 2001 + 17400 | 3500 | Fri Oct 19 10:23:54 2001 + 21400 | 4800 | Sat Oct 19 10:23:54 2002 + 17400 | 3900 | Sun Oct 19 10:23:54 2003 + 17400 | 4200 | Tue Oct 19 10:23:54 2004 + 19500 | 4500 | Wed Oct 19 10:23:54 2005 + 20500 | 4800 | Thu Oct 19 10:23:54 2006 + 16000 | 6000 | Fri Oct 19 10:23:54 2007 + 11200 | 5200 | Sun Oct 19 10:23:54 2008 +(10 rows) + +select sum(salary) over (order by enroll_timestamp range between current row and '2 years'::interval following), + salary, enroll_timestamp from empsalary; + sum | salary | enroll_timestamp +-------+--------+-------------------------- + 18500 | 5200 | Thu Oct 19 10:23:54 2000 + 17200 | 5000 | Fri Oct 19 10:23:54 2001 + 17200 | 3500 | Fri Oct 19 10:23:54 2001 + 12900 | 4800 | Sat Oct 19 10:23:54 2002 + 12600 | 3900 | Sun Oct 19 10:23:54 2003 + 13500 | 4200 | Tue Oct 19 10:23:54 2004 + 15300 | 4500 | Wed Oct 19 10:23:54 2005 + 16000 | 4800 | Thu Oct 19 10:23:54 2006 + 11200 | 6000 | Fri Oct 19 10:23:54 2007 + 5200 | 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 current row), + salary, enroll_timestamp from empsalary; + sum | salary | enroll_timestamp +-------+--------+-------------------------- + 5200 | 5200 | Thu Oct 19 10:23:54 2000 + 13700 | 5000 | Fri Oct 19 10:23:54 2001 + 13700 | 3500 | Fri Oct 19 10:23:54 2001 + 13300 | 4800 | Sat Oct 19 10:23:54 2002 + 8700 | 3900 | Sun Oct 19 10:23:54 2003 + 8100 | 4200 | Tue Oct 19 10:23:54 2004 + 8700 | 4500 | Wed Oct 19 10:23:54 2005 + 9300 | 4800 | Thu Oct 19 10:23:54 2006 + 10800 | 6000 | Fri Oct 19 10:23:54 2007 + 11200 | 5200 | Sun Oct 19 10:23:54 2008 +(10 rows) + +select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following), + salary, enroll_date from empsalary; + sum | salary | enroll_date +-------+--------+------------- + 34900 | 5000 | 10-01-2006 + 34900 | 6000 | 10-01-2006 + 38400 | 3900 | 12-23-2006 + 47100 | 4800 | 08-01-2007 + 47100 | 5200 | 08-01-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 5200 | 08-15-2007 + 47100 | 3500 | 12-10-2007 + 47100 | 4500 | 01-01-2008 + 47100 | 4200 | 01-01-2008 +(10 rows) + +select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), salary, enroll_date from empsalary; + sum | salary | enroll_date +-------+--------+------------- + 29900 | 5000 | 10-01-2006 + 28900 | 6000 | 10-01-2006 + 34500 | 3900 | 12-23-2006 + 42300 | 4800 | 08-01-2007 + 41900 | 5200 | 08-01-2007 + 42300 | 4800 | 08-08-2007 + 41900 | 5200 | 08-15-2007 + 43600 | 3500 | 12-10-2007 + 42600 | 4500 | 01-01-2008 + 42900 | 4200 | 01-01-2008 +(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 +-------+--------+------------- + 28900 | 5000 | 10-01-2006 + 29900 | 6000 | 10-01-2006 + 38400 | 3900 | 12-23-2006 + 41900 | 4800 | 08-01-2007 + 42300 | 5200 | 08-01-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 5200 | 08-15-2007 + 47100 | 3500 | 12-10-2007 + 42900 | 4500 | 01-01-2008 + 42600 | 4200 | 01-01-2008 +(10 rows) + +select first_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lead(salary) over(order by salary range between 1000 preceding and 1000 following), + nth_value(salary, 1) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary; + first_value | lead | nth_value | salary +-------------+------+-----------+-------- + 3500 | 3900 | 3500 | 3500 + 3500 | 4200 | 3500 | 3900 + 3500 | 4500 | 3500 | 4200 + 3500 | 4800 | 3500 | 4500 + 3900 | 4800 | 3900 | 4800 + 3900 | 5000 | 3900 | 4800 + 4200 | 5200 | 4200 | 5000 + 4200 | 5200 | 4200 | 5200 + 4200 | 6000 | 4200 | 5200 + 5000 | | 5000 | 6000 +(10 rows) + +select last_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lag(salary) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary; + last_value | lag | salary +------------+------+-------- + 4500 | | 3500 + 4800 | 3500 | 3900 + 5200 | 3900 | 4200 + 5200 | 4200 | 4500 + 5200 | 4500 | 4800 + 5200 | 4800 | 4800 + 6000 | 4800 | 5000 + 6000 | 5000 | 5200 + 6000 | 5200 | 5200 + 6000 | 5200 | 6000 +(10 rows) + +select first_value(salary) over(order by salary range between 1000 following and 3000 following + exclude current row), + lead(salary) over(order by salary range between 1000 following and 3000 following exclude ties), + nth_value(salary, 1) over(order by salary range between 1000 following and 3000 following + exclude ties), + salary from empsalary; + first_value | lead | nth_value | salary +-------------+------+-----------+-------- + 4500 | 3900 | 4500 | 3500 + 5000 | 4200 | 5000 | 3900 + 5200 | 4500 | 5200 | 4200 + 6000 | 4800 | 6000 | 4500 + 6000 | 4800 | 6000 | 4800 + 6000 | 5000 | 6000 | 4800 + 6000 | 5200 | 6000 | 5000 + | 5200 | | 5200 + | 6000 | | 5200 + | | | 6000 +(10 rows) + +select last_value(salary) over(order by salary range between 1000 following and 3000 following + exclude group), + lag(salary) over(order by salary range between 1000 following and 3000 following exclude group), + salary from empsalary; + last_value | lag | salary +------------+------+-------- + 6000 | | 3500 + 6000 | 3500 | 3900 + 6000 | 3900 | 4200 + 6000 | 4200 | 4500 + 6000 | 4500 | 4800 + 6000 | 4800 | 4800 + 6000 | 4800 | 5000 + | 5000 | 5200 + | 5200 | 5200 + | 5200 | 6000 +(10 rows) + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following), + salary, enroll_date from empsalary; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 5000 | 5200 | 5000 | 10-01-2006 + | 5200 | 6000 | 10-01-2006 + 5000 | 3500 | 3900 | 12-23-2006 + 5000 | 4200 | 4800 | 08-01-2007 + 5000 | 4200 | 5200 | 08-01-2007 + 5000 | 4200 | 4800 | 08-08-2007 + 5000 | 4200 | 5200 | 08-15-2007 + 5000 | 4200 | 3500 | 12-10-2007 + 5000 | 4200 | 4500 | 01-01-2008 + 5000 | 4200 | 4200 | 01-01-2008 +(10 rows) + +-- RANGE BETWEEN with values negative tests +select sum(salary) over (order by enroll_timestamp, enroll_date range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_timestamp from empsalary; +ERROR: RANGE clause requires exactly one ORDER BY column +LINE 1: select sum(salary) over (order by enroll_timestamp, enroll_d... + ^ +select sum(salary) over (range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_timestamp from empsalary; +ERROR: RANGE clause requires exactly one ORDER BY column +LINE 1: select sum(salary) over (range between '1 year'::interval pr... + ^ +select sum(salary) over (order by depname range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_timestamp from empsalary; +ERROR: ORDER BY column in window function must be an integral or date/time +select max(enroll_date) over (order by enroll_timestamp range between 1 preceding and 2 following + exclude ties), salary, enroll_timestamp from empsalary; +ERROR: Offsets must be an interval +LINE 1: select max(enroll_date) over (order by enroll_timestamp rang... + ^ +select max(enroll_date) over (order by salary range between -1 preceding and 2 following + exclude ties), salary, enroll_timestamp from empsalary; +ERROR: RANGE offsets cannot be negative. invalid value -1 +select max(enroll_date) over (order by salary range between 1 preceding and -2 following + exclude ties), salary, enroll_timestamp from empsalary; +ERROR: RANGE offsets cannot be negative. invalid value -2 +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; +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) + +select first_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lead(salary) over(order by enroll_date groups between 1 preceding and 1 following), + nth_value(salary, 1) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary; + first_value | lead | nth_value | salary | enroll_date +-------------+------+-----------+--------+------------- + 5000 | 6000 | 5000 | 5000 | 10-01-2006 + 5000 | 3900 | 5000 | 6000 | 10-01-2006 + 5000 | 4800 | 5000 | 3900 | 12-23-2006 + 3900 | 5200 | 3900 | 4800 | 08-01-2007 + 3900 | 4800 | 3900 | 5200 | 08-01-2007 + 4800 | 5200 | 4800 | 4800 | 08-08-2007 + 4800 | 3500 | 4800 | 5200 | 08-15-2007 + 5200 | 4500 | 5200 | 3500 | 12-10-2007 + 3500 | 4200 | 3500 | 4500 | 01-01-2008 + 3500 | | 3500 | 4200 | 01-01-2008 +(10 rows) + +select last_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lag(salary) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary; + last_value | lag | salary | enroll_date +------------+------+--------+------------- + 3900 | | 5000 | 10-01-2006 + 3900 | 5000 | 6000 | 10-01-2006 + 5200 | 6000 | 3900 | 12-23-2006 + 4800 | 3900 | 4800 | 08-01-2007 + 4800 | 4800 | 5200 | 08-01-2007 + 5200 | 5200 | 4800 | 08-08-2007 + 3500 | 4800 | 5200 | 08-15-2007 + 4200 | 5200 | 3500 | 12-10-2007 + 4200 | 3500 | 4500 | 01-01-2008 + 4200 | 4500 | 4200 | 01-01-2008 +(10 rows) + +select first_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude current row), + lead(salary) over(order by enroll_date groups between 1 following and 3 following exclude ties), + nth_value(salary, 1) over(order by enroll_date groups between 1 following and 3 following + exclude ties), + salary, enroll_date from empsalary; + first_value | lead | nth_value | salary | enroll_date +-------------+------+-----------+--------+------------- + 3900 | 6000 | 3900 | 5000 | 10-01-2006 + 3900 | 3900 | 3900 | 6000 | 10-01-2006 + 4800 | 4800 | 4800 | 3900 | 12-23-2006 + 4800 | 5200 | 4800 | 4800 | 08-01-2007 + 4800 | 4800 | 4800 | 5200 | 08-01-2007 + 5200 | 5200 | 5200 | 4800 | 08-08-2007 + 3500 | 3500 | 3500 | 5200 | 08-15-2007 + 4500 | 4500 | 4500 | 3500 | 12-10-2007 + | 4200 | | 4500 | 01-01-2008 + | | | 4200 | 01-01-2008 +(10 rows) + +select last_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude group), + lag(salary) over(order by enroll_date groups between 1 following and 3 following exclude group), + salary, enroll_date from empsalary; + last_value | lag | salary | enroll_date +------------+------+--------+------------- + 4800 | | 5000 | 10-01-2006 + 4800 | 5000 | 6000 | 10-01-2006 + 5200 | 6000 | 3900 | 12-23-2006 + 3500 | 3900 | 4800 | 08-01-2007 + 3500 | 4800 | 5200 | 08-01-2007 + 4200 | 5200 | 4800 | 08-08-2007 + 4200 | 4800 | 5200 | 08-15-2007 + 4200 | 5200 | 3500 | 12-10-2007 + | 3500 | 4500 | 01-01-2008 + | 4500 | 4200 | 01-01-2008 +(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 e2a1a1cdd5..03c68dacb3 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -6,20 +6,25 @@ CREATE TEMPORARY TABLE empsalary ( depname varchar, empno bigint, salary int, - enroll_date date + enroll_date date, + enroll_time time, + enroll_timetz timetz, + enroll_interval interval, + enroll_timestamptz timestamptz, + enroll_timestamp timestamp ); INSERT INTO empsalary VALUES -('develop', 10, 5200, '2007-08-01'), -('sales', 1, 5000, '2006-10-01'), -('personnel', 5, 3500, '2007-12-10'), -('sales', 4, 4800, '2007-08-08'), -('personnel', 2, 3900, '2006-12-23'), -('develop', 7, 4200, '2008-01-01'), -('develop', 9, 4500, '2008-01-01'), -('sales', 3, 4800, '2007-08-01'), -('develop', 8, 6000, '2006-10-01'), -('develop', 11, 5200, '2007-08-15'); +('develop', 10, 5200, '2007-08-01', '11:00', '11:00 BST', '1 year'::interval, TIMESTAMP '2000-10-19 10:23:54+01', TIMESTAMP '2000-10-19 10:23:54'), +('sales', 1, 5000, '2006-10-01', '12:00', '12:00 BST', '2 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'), +('personnel', 5, 3500, '2007-12-10', '13:00', '13:00 BST', '3 years'::interval, TIMESTAMP '2001-10-19 10:23:54+01', TIMESTAMP '2001-10-19 10:23:54'), +('sales', 4, 4800, '2007-08-08', '14:00', '14:00 BST', '4 years'::interval, TIMESTAMP '2002-10-19 10:23:54+01', TIMESTAMP '2002-10-19 10:23:54'), +('personnel', 2, 3900, '2006-12-23', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2003-10-19 10:23:54+01', TIMESTAMP '2003-10-19 10:23:54'), +('develop', 7, 4200, '2008-01-01', '15:00', '15:00 BST', '5 years'::interval, TIMESTAMP '2004-10-19 10:23:54+01', TIMESTAMP '2004-10-19 10:23:54'), +('develop', 9, 4500, '2008-01-01', '17:00', '17:00 BST', '7 years'::interval, TIMESTAMP '2005-10-19 10:23:54+01', TIMESTAMP '2005-10-19 10:23:54'), +('sales', 3, 4800, '2007-08-01', '18:00', '18:00 BST', '8 years'::interval, TIMESTAMP '2006-10-19 10:23:54+01', TIMESTAMP '2006-10-19 10:23:54'), +('develop', 8, 6000, '2006-10-01', '19:00', '19:00 BST', '9 years'::interval, TIMESTAMP '2007-10-19 10:23:54+01', TIMESTAMP '2007-10-19 10:23:54'), +('develop', 11, 5200, '2007-08-15', '20:00', '20:00 BST', '10 years'::interval, TIMESTAMP '2008-10-19 10:23:54+01', TIMESTAMP '2008-10-19 10:23:54'); SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname) FROM empsalary ORDER BY depname, salary; @@ -189,6 +194,22 @@ SELECT sum(unique1) over (rows between 2 preceding and 2 following), unique1, four FROM tenk1 WHERE unique1 < 10; +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude no others), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT sum(unique1) over (rows between 2 preceding and 2 following exclude current row), + 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; + SELECT sum(unique1) over (rows between 2 preceding and 1 preceding), unique1, four FROM tenk1 WHERE unique1 < 10; @@ -205,10 +226,17 @@ SELECT sum(unique1) over (w range between current row and unbounded following), unique1, four FROM tenk1 WHERE unique1 < 10 WINDOW w AS (order by four); --- fail: not implemented yet -SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding), +SELECT sum(unique1) over (w range between unbounded preceding and current row exclude current row), unique1, four -FROM tenk1 WHERE unique1 < 10; +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); SELECT first_value(unique1) over w, nth_value(unique1, 2) over w AS nth_2, @@ -230,6 +258,354 @@ 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 current row) 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 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; + +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 no others) 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 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 +FROM tenk1 WHERE unique1 < 10; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude current row), + 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; + +SELECT sum(unique1) over (order by four range between 2::int8 preceding and 6::int2 following exclude ties), + 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; + +SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following + exclude current row),unique1, four +FROM tenk1 WHERE unique1 < 10; + +select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following), + 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 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), + 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 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), + 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 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), + 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 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), + 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 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), + 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 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), + salary, enroll_timestamp from empsalary; + +select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and current row), + salary, enroll_timestamp from empsalary; + +select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following), + salary, enroll_date from empsalary; + +select sum(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + 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; + +select first_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lead(salary) over(order by salary range between 1000 preceding and 1000 following), + nth_value(salary, 1) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary; + +select last_value(salary) over(order by salary range between 1000 preceding and 1000 following), + lag(salary) over(order by salary range between 1000 preceding and 1000 following), + salary from empsalary; + +select first_value(salary) over(order by salary range between 1000 following and 3000 following + exclude current row), + lead(salary) over(order by salary range between 1000 following and 3000 following exclude ties), + nth_value(salary, 1) over(order by salary range between 1000 following and 3000 following + exclude ties), + salary from empsalary; + +select last_value(salary) over(order by salary range between 1000 following and 3000 following + exclude group), + lag(salary) over(order by salary range between 1000 following and 3000 following exclude group), + salary from empsalary; + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude ties), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following), + salary, enroll_date from empsalary; + +-- RANGE BETWEEN with values negative tests +select sum(salary) over (order by enroll_timestamp, enroll_date range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_timestamp from empsalary; + +select sum(salary) over (range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_timestamp from empsalary; + +select sum(salary) over (order by depname range between '1 year'::interval preceding and '2 years'::interval following + exclude ties), salary, enroll_timestamp from empsalary; + +select max(enroll_date) over (order by enroll_timestamp range between 1 preceding and 2 following + exclude ties), salary, enroll_timestamp from empsalary; + +select max(enroll_date) over (order by salary range between -1 preceding and 2 following + exclude ties), salary, enroll_timestamp from empsalary; + +select max(enroll_date) over (order by salary range between 1 preceding and -2 following + exclude ties), salary, enroll_timestamp from empsalary; + +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; + +select first_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lead(salary) over(order by enroll_date groups between 1 preceding and 1 following), + nth_value(salary, 1) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary; + +select last_value(salary) over(order by enroll_date groups between 1 preceding and 1 following), + lag(salary) over(order by enroll_date groups between 1 preceding and 1 following), + salary, enroll_date from empsalary; + +select first_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude current row), + lead(salary) over(order by enroll_date groups between 1 following and 3 following exclude ties), + nth_value(salary, 1) over(order by enroll_date groups between 1 following and 3 following + exclude ties), + salary, enroll_date from empsalary; + +select last_value(salary) over(order by enroll_date groups between 1 following and 3 following + exclude group), + lag(salary) over(order by enroll_date groups between 1 following and 3 following exclude group), + salary, enroll_date from empsalary; + +-- 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;