diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 487c7ff750..d6fd518862 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -14729,8 +14729,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..3381dca3f2 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,37 @@ 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 of a supported type. In all three 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..162ee04228 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,30 @@ 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 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 0afb1c83d3..0500253b28 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -157,6 +157,12 @@ typedef struct WindowStatePerAggData bool restart; /* need to restart this agg in this cycle? */ } WindowStatePerAggData; +/* Constants for row_is_in_frame() return values */ +#define ROW_ALL_OUT_FRAME -1 +#define ROW_CURRENT_OUT_FRAME 0 +#define ROW_EXCLUDED_FRAME 1 +#define ROW_IN_FRAME 2 + static void initialize_windowaggregate(WindowAggState *winstate, WindowStatePerFunc perfuncstate, WindowStatePerAgg peraggstate); @@ -180,7 +186,9 @@ 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 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 +691,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 +742,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 +773,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 EXCLUSION 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 +790,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 +933,8 @@ eval_windowaggregates(WindowAggState *winstate) */ for (;;) { + int ret; + /* Fetch next row if we didn't already */ if (TupIsNull(agg_row_slot)) { @@ -928,9 +943,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 == ROW_ALL_OUT_FRAME) break; + else if (ret == ROW_CURRENT_OUT_FRAME || ret == ROW_EXCLUDED_FRAME) + goto next_tuple; /* Set tuple context for evaluation of aggregate arguments */ winstate->tmpcontext->ecxt_outertuple = agg_row_slot; @@ -951,6 +972,7 @@ eval_windowaggregates(WindowAggState *winstate) peraggstate); } + next_tuple: /* Reset per-input-tuple context after each tuple */ ResetExprContext(winstate->tmpcontext); @@ -1113,7 +1135,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 +1180,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 +1378,71 @@ release_partition(WindowAggState *winstate) tuplestore_end(winstate->buffer); winstate->buffer = NULL; winstate->partition_spooled = false; + + if (winstate->frameOptions & (FRAMEOPTION_GROUPS_BETWEEN | FRAMEOPTION_RANGE_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; } /* @@ -1275,8 +1453,14 @@ 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: + * ROW_ALL_OUT_FRAME, if the row is out of frame and no succeeding rows can be in frame + * ROW_CURRENT_OUT_FRAME, if the row is out of frame but succeeding rows might be in frame + * ROW_EXCLUDED_FRAME, if the row would have been in frame, but matched an EXCLUDE clause + * ROW_IN_FRAME, if the row is in frame */ -static bool +static int row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot) { int frameOptions = winstate->frameOptions; @@ -1290,35 +1474,64 @@ 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 ROW_ALL_OUT_FRAME; } - 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 ROW_ALL_OUT_FRAME; } 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 ROW_CURRENT_OUT_FRAME; } else if (frameOptions & FRAMEOPTION_RANGE) { - /* parser should have rejected this */ - elog(ERROR, "window frame with value offset is not implemented"); + bool isnull, + inrange, + preceding; + Datum slotval, + currval; + + slotval = slot_getattr(slot, 1, &isnull); + if (isnull) + return ROW_CURRENT_OUT_FRAME; + currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull); + if (isnull) + return ROW_CURRENT_OUT_FRAME; + preceding = (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) != 0 ? true : false; + inrange = DatumGetBool(DirectFunctionCall5(winstate->startRangeFunc, + currval, + slotval, + winstate->startOffsetValue, + BoolGetDatum(winstate->sortByAsc ? + preceding : !preceding), + BoolGetDatum(winstate->sortByAsc ? + false : true))); + if (!inrange) + return ROW_CURRENT_OUT_FRAME; + } + 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 ROW_CURRENT_OUT_FRAME; } else Assert(false); @@ -1331,42 +1544,86 @@ 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 ROW_ALL_OUT_FRAME; } - 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 ROW_ALL_OUT_FRAME; } 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 ROW_ALL_OUT_FRAME; } else if (frameOptions & FRAMEOPTION_RANGE) { - /* parser should have rejected this */ - elog(ERROR, "window frame with value offset is not implemented"); + bool isnull, + inrange, + preceding; + Datum slotval, + currval; + + slotval = slot_getattr(slot, 1, &isnull); + if (isnull) + return ROW_ALL_OUT_FRAME; + currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull); + if (isnull) + return ROW_ALL_OUT_FRAME; + preceding = (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) != 0 ? true : false; + inrange = DatumGetBool(DirectFunctionCall5(winstate->endRangeFunc, + currval, + slotval, + winstate->endOffsetValue, + BoolGetDatum(winstate->sortByAsc ? + preceding : !preceding), + BoolGetDatum(winstate->sortByAsc ? + true : false))); + if (!inrange) + return ROW_ALL_OUT_FRAME; + } + 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 ROW_ALL_OUT_FRAME; } else Assert(false); } + /* Check exclusion clause */ + if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT) + { + if (pos == winstate->currentpos) + return ROW_EXCLUDED_FRAME; + } else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP) + { + if (are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot)) + return ROW_EXCLUDED_FRAME; + } + else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES) + { + if ((pos != winstate->currentpos) && + are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot)) + return ROW_EXCLUDED_FRAME; + } + /* If we get here, it's in frame */ - return true; + return ROW_IN_FRAME; } /* @@ -1402,7 +1659,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 +1698,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 +1721,48 @@ 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, + inrange, + preceding; + Datum slotval, + currval; + window_gettupleslot(winobj, winstate->frameheadpos, slot); + preceding = (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) ? true : false; + + 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; + inrange = DatumGetBool(DirectFunctionCall5(winstate->startRangeFunc, + currval, + slotval, + winstate->startOffsetValue, + BoolGetDatum(winstate->sortByAsc ? + preceding : !preceding), + BoolGetDatum(winstate->sortByAsc ? + false : true))); + if (inrange) + 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 +1805,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 +1842,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 +1865,64 @@ 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, + inrange, + preceding; + Datum slotval, + currval; + preceding = (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) ? true : false; + + 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; + inrange = DatumGetBool(DirectFunctionCall5(winstate->endRangeFunc, + currval, + slotval, + winstate->endOffsetValue, + BoolGetDatum(winstate->sortByAsc ? + preceding : !preceding), + BoolGetDatum(winstate->sortByAsc ? + true : false))); + if (!inrange) + 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 +1980,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 +2005,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); @@ -1782,6 +2135,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) numaggs, aggno; ListCell *l; + FmgrInfo finfo; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -1990,6 +2344,18 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) winstate->agg_winobj = agg_winobj; } + if (node->startRangeOid != 0) + { + fmgr_info(node->startRangeOid, &finfo); + winstate->startRangeFunc = finfo.fn_addr; + } + if (node->endRangeOid != 0) + { + fmgr_info(node->endRangeOid, &finfo); + winstate->endRangeFunc = finfo.fn_addr; + } + winstate->sortByAsc = node->sortByAsc; + /* copy frame options to state node for easy access */ winstate->frameOptions = node->frameOptions; @@ -2710,6 +3076,15 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno, } } +#define FUNC_ARG_RETURN_FALSE \ +do \ +{ \ + if (isout) \ + *isout = true; \ + *isnull = true; \ + return (Datum) 0; \ +} while (0) + /* * WinGetFuncArgInFrame * Evaluate a window function's argument expression on a specified @@ -2737,7 +3112,9 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, ExprContext *econtext; TupleTableSlot *slot; bool gottuple; - int64 abs_pos; + int64 abs_pos, + temppos; + int inframe = ROW_ALL_OUT_FRAME; Assert(WindowObjectIsValid(winobj)); winstate = winobj->winstate; @@ -2747,7 +3124,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, switch (seektype) { case WINDOW_SEEK_CURRENT: - abs_pos = winstate->currentpos + relpos; + elog(ERROR, "WINDOW_SEEK_CURRENT is invalid for WinGetFuncArgInFrfame"); break; case WINDOW_SEEK_HEAD: update_frameheadpos(winobj, slot); @@ -2763,50 +3140,70 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno, break; } + temppos = abs_pos; gottuple = window_gettupleslot(winobj, abs_pos, slot); if (gottuple) - gottuple = row_is_in_frame(winstate, abs_pos, slot); + inframe = row_is_in_frame(winstate, abs_pos, slot); + else + FUNC_ARG_RETURN_FALSE; - if (!gottuple) + /* + * If the row matches an EXCLUDE clause, retrieve further rows + * until one is returned that does not match an EXCLUDE clause, + * either by being in frame or out of frame. + */ + while (inframe == ROW_EXCLUDED_FRAME) { - if (isout) - *isout = true; - *isnull = true; - return (Datum) 0; + if (seektype == WINDOW_SEEK_HEAD) + temppos++; + else + temppos--; + gottuple = window_gettupleslot(winobj, temppos, slot); + if (gottuple) + inframe = row_is_in_frame(winstate, temppos, slot); + else + FUNC_ARG_RETURN_FALSE; } - else + + if (inframe == ROW_ALL_OUT_FRAME || inframe == ROW_CURRENT_OUT_FRAME) + FUNC_ARG_RETURN_FALSE; + + if (isout) + *isout = false; + + /* + * mark_pos is not updated in seek tail mode when there is an + * EXCLUDE clause because in that case, we need to search backwards. + */ + if (set_mark && !(seektype == WINDOW_SEEK_TAIL && + (winstate->frameOptions & FRAMEOPTION_EXCLUSION))) { - if (isout) - *isout = false; - if (set_mark) - { - int frameOptions = winstate->frameOptions; - int64 mark_pos = abs_pos; + int frameOptions = winstate->frameOptions; + int64 mark_pos = abs_pos; - /* - * In RANGE mode with a moving frame head, we must not let the - * mark advance past frameheadpos, since that row has to be - * fetchable during future update_frameheadpos calls. - * - * XXX it is very ugly to pollute window functions' marks with - * this consideration; it could for instance mask a logic bug that - * lets a window function fetch rows before what it had claimed - * was its mark. Perhaps use a separate mark for frame head - * probes? - */ - if ((frameOptions & FRAMEOPTION_RANGE) && - !(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)) - { - update_frameheadpos(winobj, winstate->temp_slot_2); - if (mark_pos > winstate->frameheadpos) - mark_pos = winstate->frameheadpos; - } - WinSetMarkPosition(winobj, mark_pos); + /* + * In RANGE mode with a moving frame head, we must not let the + * mark advance past frameheadpos, since that row has to be + * fetchable during future update_frameheadpos calls. + * + * XXX it is very ugly to pollute window functions' marks with + * this consideration; it could for instance mask a logic bug that + * lets a window function fetch rows before what it had claimed + * was its mark. Perhaps use a separate mark for frame head + * probes? + */ + if ((frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) && + !(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)) + { + update_frameheadpos(winobj, winstate->temp_slot_2); + if (mark_pos > winstate->frameheadpos) + mark_pos = winstate->frameheadpos; } - econtext->ecxt_outertuple = slot; - return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), - econtext, isnull); + WinSetMarkPosition(winobj, mark_pos); } + econtext->ecxt_outertuple = slot; + return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), + econtext, isnull); } /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index fd3001c493..d929679865 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1009,6 +1009,9 @@ _copyWindowAgg(const WindowAgg *from) COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber)); COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid)); } + COPY_SCALAR_FIELD(startRangeOid); + COPY_SCALAR_FIELD(endRangeOid); + COPY_SCALAR_FIELD(sortByAsc); COPY_SCALAR_FIELD(frameOptions); COPY_NODE_FIELD(startOffset); COPY_NODE_FIELD(endOffset); @@ -2409,6 +2412,9 @@ _copyWindowClause(const WindowClause *from) COPY_STRING_FIELD(refname); COPY_NODE_FIELD(partitionClause); COPY_NODE_FIELD(orderClause); + COPY_SCALAR_FIELD(startRangeOid); + COPY_SCALAR_FIELD(endRangeOid); + COPY_SCALAR_FIELD(sortByAsc); 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 7d2aa1a2d3..09314824af 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2731,6 +2731,9 @@ _equalWindowClause(const WindowClause *a, const WindowClause *b) COMPARE_STRING_FIELD(refname); COMPARE_NODE_FIELD(partitionClause); COMPARE_NODE_FIELD(orderClause); + COMPARE_SCALAR_FIELD(startRangeOid); + COMPARE_SCALAR_FIELD(endRangeOid); + COMPARE_SCALAR_FIELD(sortByAsc); 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 e0f4befd9f..0466536cf2 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -837,6 +837,9 @@ _outWindowAgg(StringInfo str, const WindowAgg *node) for (i = 0; i < node->ordNumCols; i++) appendStringInfo(str, " %u", node->ordOperators[i]); + WRITE_OID_FIELD(startRangeOid); + WRITE_OID_FIELD(endRangeOid); + WRITE_BOOL_FIELD(sortByAsc); WRITE_INT_FIELD(frameOptions); WRITE_NODE_FIELD(startOffset); WRITE_NODE_FIELD(endOffset); @@ -2981,6 +2984,9 @@ _outWindowClause(StringInfo str, const WindowClause *node) WRITE_STRING_FIELD(refname); WRITE_NODE_FIELD(partitionClause); WRITE_NODE_FIELD(orderClause); + WRITE_OID_FIELD(startRangeOid); + WRITE_OID_FIELD(endRangeOid); + WRITE_BOOL_FIELD(sortByAsc); 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 22d8b9d0d5..ba6535d1e0 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -366,6 +366,9 @@ _readWindowClause(void) READ_STRING_FIELD(refname); READ_NODE_FIELD(partitionClause); READ_NODE_FIELD(orderClause); + READ_OID_FIELD(startRangeOid); + READ_OID_FIELD(endRangeOid); + READ_BOOL_FIELD(sortByAsc); READ_INT_FIELD(frameOptions); READ_NODE_FIELD(startOffset); READ_NODE_FIELD(endOffset); @@ -2136,6 +2139,9 @@ _readWindowAgg(void) READ_INT_FIELD(ordNumCols); READ_ATTRNUMBER_ARRAY(ordColIdx, local_node->ordNumCols); READ_OID_ARRAY(ordOperators, local_node->ordNumCols); + READ_OID_FIELD(startRangeOid); + READ_OID_FIELD(endRangeOid); + READ_BOOL_FIELD(sortByAsc); 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 c46e1318a6..74aea78b06 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 startRangeOid, Oid endRangeOid, bool sortByAsc, + int frameOptions, Node *startOffset, Node *endOffset, Plan *lefttree); static Group *make_group(List *tlist, List *qual, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Plan *lefttree); @@ -2120,6 +2120,9 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path) ordNumCols, ordColIdx, ordOperators, + wc->startRangeOid, + wc->endRangeOid, + wc->sortByAsc, wc->frameOptions, wc->startOffset, wc->endOffset, @@ -6079,8 +6082,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 startRangeOid, Oid endRangeOid, bool sortByAsc, + int frameOptions, Node *startOffset, Node *endOffset, Plan *lefttree) { WindowAgg *node = makeNode(WindowAgg); Plan *plan = &node->plan; @@ -6092,6 +6095,9 @@ make_windowagg(List *tlist, Index winref, node->ordNumCols = ordNumCols; node->ordColIdx = ordColIdx; node->ordOperators = ordOperators; + node->startRangeOid = startRangeOid; + node->endRangeOid = endRangeOid; + node->sortByAsc = sortByAsc; node->frameOptions = frameOptions; node->startOffset = startOffset; node->endOffset = endOffset; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 5329432f25..66f8e71b15 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 '*' '/' '%' @@ -14003,7 +14003,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 @@ -14023,33 +14023,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*/ @@ -14166,6 +14176,29 @@ 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 { $$ = NULL; } + | /*EMPTY*/ { $$ = NULL; } + ; + /* * Supporting nonterminals for expressions. @@ -15027,6 +15060,7 @@ unreserved_keyword: | GENERATED | GLOBAL | GRANTED + | GROUPS | HANDLER | HEADER_P | HOLD @@ -15092,6 +15126,7 @@ unreserved_keyword: | OPTION | OPTIONS | ORDINALITY + | OTHERS | OVER | OVERRIDING | OWNED @@ -15182,6 +15217,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 6a9f1b0217..747139489a 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 406cd1dad0..8b174ccc8a 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -18,12 +18,15 @@ #include "miscadmin.h" #include "access/heapam.h" +#include "access/htup_details.h" #include "access/tsmapi.h" #include "catalog/catalog.h" #include "catalog/heap.h" #include "catalog/pg_am.h" +#include "catalog/pg_amproc.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint_fn.h" +#include "catalog/pg_opfamily.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "nodes/makefuncs.h" @@ -45,6 +48,7 @@ #include "rewrite/rewriteManip.h" #include "utils/guc.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" #include "utils/rel.h" @@ -96,6 +100,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 void getRangeBetweenFuncOid(ParseState *pstate, WindowDef *windef, + SortBy *sortby, Node *offset, Oid sortOid, Oid *rangeOid); /* @@ -2608,6 +2614,26 @@ transformSortClause(ParseState *pstate, return sortlist; } +static void +getRangeBetweenFuncOid(ParseState *pstate, WindowDef *windef, SortBy *sortby, + Node *offset, Oid sortOid, Oid *rangeOid) +{ + HeapTuple proctup; + Form_pg_amproc procform; + Oid offsetOid = exprType(offset); + + proctup = SearchSysCache4(AMPROCNUM, ObjectIdGetDatum(IN_RANGE_BTREE_FAM_OID), + sortOid, offsetOid, 1); + if (!HeapTupleIsValid(proctup)) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("no in_range function for given types"), + parser_errposition(pstate, windef->location))); + procform = (Form_pg_amproc) GETSTRUCT(proctup); + *rangeOid = procform->amproc; + ReleaseSysCache(proctup); +} + /* * transformWindowDefinitions - * transform window definitions (WindowDef to WindowClause) @@ -2760,6 +2786,38 @@ transformWindowDefinitions(ParseState *pstate, windef->endOffset); wc->winref = winref; + /* + * The RANGE BETWEEN clause requires exactly one ORDER BY column, + * and start/end values that 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) + getRangeBetweenFuncOid(pstate, windef, sortby, wc->startOffset, sortOid, &wc->startRangeOid); + if (wc->frameOptions & FRAMEOPTION_END_VALUE) + getRangeBetweenFuncOid(pstate, windef, sortby, wc->endOffset, sortOid, &wc->endRangeOid); + + if (sortby->sortby_dir != SORTBY_DESC) + wc->sortByAsc = true; + else + wc->sortByAsc = false; + } + result = lappend(result, wc); } @@ -3515,14 +3573,18 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause) { /* Transform the raw expression tree */ node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE); + constructName = "RANGE"; + } + else if (frameOptions & FRAMEOPTION_GROUPS) + { + /* Transform the raw expression tree */ + node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_GROUPS); /* - * this needs a lot of thought to decide how to support in the context - * of Postgres' extensible datatype framework + * Like LIMIT clause, simply coerce to int8 */ - constructName = "RANGE"; - /* error was already thrown by gram.y, this is just a backstop */ - elog(ERROR, "window frame with value offset is not implemented"); + 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 b2f5e46e3b..d45926f27f 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 ffae0f3cf3..4a7bc77c0f 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/date.c b/src/backend/utils/adt/date.c index 747ef49789..1f8bf3d5af 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -44,6 +44,9 @@ static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result); static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result); static void AdjustTimeForTypmod(TimeADT *time, int32 typmod); +static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end, + PGFunction miIntervalFunc, PGFunction plIntervalFunc, + PGFunction gtComparatorFunc, PGFunction ltComparatorFunc); /* common code for timetypmodin and timetztypmodin */ @@ -2754,3 +2757,61 @@ timetz_izone(PG_FUNCTION_ARGS) PG_RETURN_TIMETZADT_P(result); } + + +/*---------------------------------------------------------- + * In RANGE procedures on dates. + *---------------------------------------------------------*/ + +static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end, + PGFunction miIntervalFunc, PGFunction plIntervalFunc, + PGFunction gtComparatorFunc, PGFunction ltComparatorFunc) +{ + Datum result; + + if (preceding) + result = DirectFunctionCall2(miIntervalFunc, curr, offset); + else + result = DirectFunctionCall2(plIntervalFunc, curr, offset); + + if (end) + { + if (DatumGetBool(DirectFunctionCall2(gtComparatorFunc, slot, result))) + return false; + } + else + { + if (DatumGetBool(DirectFunctionCall2(ltComparatorFunc, slot, result))) + return false; + } + + return true; +} + +Datum +in_range_timetz_interval(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2), + PG_GETARG_BOOL(3), PG_GETARG_BOOL(4), + timetz_mi_interval, timetz_pl_interval, + timetz_gt, timetz_lt)); +} + +Datum +in_range_date_interval(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), + DirectFunctionCall1(date_timestamp, PG_GETARG_DATUM(1)), + PG_GETARG_DATUM(2), PG_GETARG_BOOL(3), PG_GETARG_BOOL(4), + date_mi_interval, date_pl_interval, + timestamp_gt, timestamp_lt)); +} + +Datum +in_range_time_interval(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(in_range_internal(DirectFunctionCall1(time_interval, PG_GETARG_DATUM(0)), + DirectFunctionCall1(time_interval, PG_GETARG_DATUM(1)), + PG_GETARG_DATUM(2), PG_GETARG_BOOL(3), PG_GETARG_BOOL(4), + interval_mi, interval_pl, interval_gt, interval_lt)); +} diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index 7352908365..18ea7fc91c 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -307,6 +307,119 @@ int4send(PG_FUNCTION_ARGS) } +/*---------------------------------------------------------- + * In RANGE procedures on 16-bit and 32-bit integers. + *---------------------------------------------------------*/ + +Datum +in_range_int4_int4(PG_FUNCTION_ARGS) +{ + int32 curr = PG_GETARG_INT32(0); + int32 slot = PG_GETARG_INT32(1); + int32 offset = PG_GETARG_INT32(2); + bool preceding = PG_GETARG_BOOL(3); + bool end = PG_GETARG_BOOL(4); + int32 result; + + if (offset < 0) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("RANGE offsets cannot be negative. invalid value %d", offset))); + if (preceding) + offset = -offset; + + if (unlikely(pg_add_s32_overflow(curr, offset, &result))) + PG_RETURN_BOOL(true); + + if (end) + { + if (slot > result) + PG_RETURN_BOOL(false); + } + else + { + if (slot < result) + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} + +Datum +in_range_int4_int8(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DirectFunctionCall5(in_range_int4_int4, PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + DirectFunctionCall1(int84, PG_GETARG_DATUM(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + +Datum +in_range_int4_int2(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DirectFunctionCall5(in_range_int4_int4, PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + DirectFunctionCall1(i2toi4, PG_GETARG_DATUM(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + +Datum +in_range_int2_int2(PG_FUNCTION_ARGS) +{ + int16 curr = PG_GETARG_INT16(0); + int16 slot = PG_GETARG_INT16(1); + int16 offset = PG_GETARG_INT16(2); + bool preceding = PG_GETARG_BOOL(3); + bool end = PG_GETARG_BOOL(4); + int16 result; + + if (offset < 0) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("RANGE offsets cannot be negative. invalid value %d", offset))); + if (preceding) + offset = -offset; + + if (unlikely(pg_add_s16_overflow(curr, offset, &result))) + PG_RETURN_BOOL(true); + + if (end) + { + if (slot > result) + PG_RETURN_BOOL(false); + } + else + { + if (slot < result) + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} + +Datum +in_range_int2_int8(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DirectFunctionCall5(in_range_int2_int2, PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + DirectFunctionCall1(int82, PG_GETARG_DATUM(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + +Datum +in_range_int2_int4(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DirectFunctionCall5(in_range_int2_int2, PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + DirectFunctionCall1(i4toi2, PG_GETARG_DATUM(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + + /* * =================== * CONVERSION ROUTINES diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index ae6a4683d4..1e82ab5104 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1101,6 +1101,66 @@ int8shr(PG_FUNCTION_ARGS) PG_RETURN_INT64(arg1 >> arg2); } + +/*---------------------------------------------------------- + * In RANGE procedures on 64-bit integers. + *---------------------------------------------------------*/ + +Datum +in_range_int8_int8(PG_FUNCTION_ARGS) +{ + int64 curr = PG_GETARG_INT64(0); + int64 slot = PG_GETARG_INT64(1); + int64 offset = PG_GETARG_INT64(2); + bool preceding = PG_GETARG_BOOL(3); + bool end = PG_GETARG_BOOL(4); + int64 result; + + if (offset < 0) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("RANGE offsets cannot be negative. invalid value %zd", offset))); + if (preceding) + offset = -offset; + + if (unlikely(pg_add_s64_overflow(curr, offset, &result))) + PG_RETURN_BOOL(true); + + if (end) + { + if (slot > result) + PG_RETURN_BOOL(false); + } + else + { + if (slot < result) + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} + +Datum +in_range_int8_int4(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DirectFunctionCall5(in_range_int8_int8, PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + DirectFunctionCall1(int48, PG_GETARG_DATUM(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + +Datum +in_range_int8_int2(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(DirectFunctionCall5(in_range_int8_int8, PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1), + DirectFunctionCall1(int28, PG_GETARG_DATUM(2)), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + + /*---------------------------------------------------------- * Conversion operators. *---------------------------------------------------------*/ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c5f5a1ca3f..3d12a35a22 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -5877,6 +5877,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) @@ -5917,6 +5919,12 @@ 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 "); /* we will now have a trailing space; remove it */ buf->len--; } diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index e6a1eed191..b1eb6c688f 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -74,6 +74,9 @@ static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); static void AdjustIntervalForTypmod(Interval *interval, int32 typmod); static TimestampTz timestamp2timestamptz(Timestamp timestamp); static Timestamp timestamptz2timestamp(TimestampTz timestamp); +static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end, + PGFunction miIntervalFunc, PGFunction plIntervalFunc, + PGFunction gtComparatorFunc, PGFunction ltComparatorFunc); /* common code for timestamptypmodin and timestamptztypmodin */ @@ -5148,6 +5151,63 @@ timestamptz_izone(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMP(result); } +/*---------------------------------------------------------- + * In RANGE procedures on timestamps. + *---------------------------------------------------------*/ + +static bool in_range_internal(Datum curr, Datum slot, Datum offset, bool preceding, bool end, + PGFunction miIntervalFunc, PGFunction plIntervalFunc, + PGFunction gtComparatorFunc, PGFunction ltComparatorFunc) +{ + Datum result; + + if (preceding) + result = DirectFunctionCall2(miIntervalFunc, curr, offset); + else + result = DirectFunctionCall2(plIntervalFunc, curr, offset); + + if (end) + { + if (DatumGetBool(DirectFunctionCall2(gtComparatorFunc, slot, result))) + return false; + } + else + { + if (DatumGetBool(DirectFunctionCall2(ltComparatorFunc, slot, result))) + return false; + } + + return true; +} + +Datum +in_range_timestamp_interval(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2), + PG_GETARG_BOOL(3), PG_GETARG_BOOL(4), + timestamp_mi_interval, timestamp_pl_interval, + timestamp_gt, timestamp_lt)); +} + +Datum +in_range_timestamptz_interval(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2), + PG_GETARG_BOOL(3), PG_GETARG_BOOL(4), + timestamptz_mi_interval, timestamptz_pl_interval, + timestamp_gt, timestamp_lt)); +} + +Datum +in_range_interval_interval(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(in_range_internal(PG_GETARG_DATUM(0), PG_GETARG_DATUM(1), PG_GETARG_DATUM(2), + PG_GETARG_BOOL(3), PG_GETARG_BOOL(4), + interval_mi, interval_pl, + interval_gt, interval_lt)); +} + + /* generate_series_timestamp() * Generate the set of timestamps from start to finish by step */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index f545a0580d..cad37b4ed0 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -566,5 +566,21 @@ DATA(insert ( 4104 603 603 3 4107 )); DATA(insert ( 4104 603 603 4 4108 )); DATA(insert ( 4104 603 603 11 4067 )); DATA(insert ( 4104 603 603 13 187 )); +/* in RANGE functions */ +DATA(insert ( 5009 20 20 1 6122 )); +DATA(insert ( 5009 20 23 1 6123 )); +DATA(insert ( 5009 20 21 1 6124 )); +DATA(insert ( 5009 23 20 1 6125 )); +DATA(insert ( 5009 23 23 1 6126 )); +DATA(insert ( 5009 23 21 1 6127 )); +DATA(insert ( 5009 21 20 1 6128 )); +DATA(insert ( 5009 21 23 1 6129 )); +DATA(insert ( 5009 21 21 1 6130 )); +DATA(insert ( 5009 1114 1186 1 6131 )); +DATA(insert ( 5009 1184 1186 1 6132 )); +DATA(insert ( 5009 1186 1186 1 6133 )); +DATA(insert ( 5009 1266 1186 1 6134 )); +DATA(insert ( 5009 1082 1186 1 6135 )); +DATA(insert ( 5009 1083 1186 1 6136 )); #endif /* PG_AMPROC_H */ diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index b544474254..9086fea15f 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -187,5 +187,7 @@ DATA(insert OID = 4082 ( 3580 pg_lsn_minmax_ops PGNSP PGUID )); DATA(insert OID = 4104 ( 3580 box_inclusion_ops PGNSP PGUID )); DATA(insert OID = 5000 ( 4000 box_ops PGNSP PGUID )); DATA(insert OID = 5008 ( 4000 poly_ops PGNSP PGUID )); +DATA(insert OID = 5009 ( 403 in_range_ops PGNSP PGUID )); +#define IN_RANGE_BTREE_FAM_OID 5009 #endif /* PG_OPFAMILY_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index f01648c961..7b18d837d9 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -2701,6 +2701,38 @@ DESCR("aggregate final function"); DATA(insert OID = 3545 ( string_agg PGNSP PGUID 12 1 0 0 0 t f f f f f i s 2 0 17 "17 17" _null_ _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); DESCR("concatenate aggregate input into a bytea"); +/* In RANGE functions */ +DATA(insert OID = 6122 ( in_range_int8_int8 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int8 _null_ _null_ _null_ )); +DESCR("in range for int8 column, int8 offset"); +DATA(insert OID = 6123 ( in_range_int8_int4 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int4 _null_ _null_ _null_ )); +DESCR("in range for int8 column, int4 offset"); +DATA(insert OID = 6124 ( in_range_int8_int2 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "20 20 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int8_int2 _null_ _null_ _null_ )); +DESCR("in range for int8 column, int2 offset"); +DATA(insert OID = 6125 ( in_range_int4_int8 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int8 _null_ _null_ _null_ )); +DESCR("in range for int4 column, int8 offset"); +DATA(insert OID = 6126 ( in_range_int4_int4 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int4 _null_ _null_ _null_ )); +DESCR("in range for int4 column, int4 offset"); +DATA(insert OID = 6127 ( in_range_int4_int2 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "23 23 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int4_int2 _null_ _null_ _null_ )); +DESCR("in range for int4 column, int2 offset"); +DATA(insert OID = 6128 ( in_range_int2_int8 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 20 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int8 _null_ _null_ _null_ )); +DESCR("in range for int2 column, int8 offset"); +DATA(insert OID = 6129 ( in_range_int2_int4 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 23 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int4 _null_ _null_ _null_ )); +DESCR("in range for int2 column, int4 offset"); +DATA(insert OID = 6130 ( in_range_int2_int2 PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "21 21 21 16 16" _null_ _null_ _null_ _null_ _null_ in_range_int2_int2 _null_ _null_ _null_ )); +DESCR("in range for int2 column, int2 offset"); +DATA(insert OID = 6131 ( in_range_timestamp_interval PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1114 1114 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timestamp_interval _null_ _null_ _null_ )); +DESCR("in range for timestamp column, interval offset"); +DATA(insert OID = 6132 ( in_range_timestamptz_interval PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1184 1184 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timestamptz_interval _null_ _null_ _null_ )); +DESCR("in range for timestamptz column, interval offset"); +DATA(insert OID = 6133 ( in_range_interval_interval PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1186 1186 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_interval_interval _null_ _null_ _null_ )); +DESCR("in range for interval column, interval offset"); +DATA(insert OID = 6134 ( in_range_timetz_interval PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1266 1266 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_timetz_interval _null_ _null_ _null_ )); +DESCR("in range for timetz column, interval offset"); +DATA(insert OID = 6135 ( in_range_date_interval PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1082 1082 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_date_interval _null_ _null_ _null_ )); +DESCR("in range for date column, interval offset"); +DATA(insert OID = 6136 ( in_range_time_interval PGNSP PGUID 12 1 0 0 0 f f f t t f i s 5 0 16 "1083 1083 1186 16 16" _null_ _null_ _null_ _null_ _null_ in_range_time_interval _null_ _null_ _null_ )); +DESCR("in range for time column, interval offset"); + /* To ASCII conversion */ DATA(insert OID = 1845 ( to_ascii PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ to_ascii_default _null_ _null_ _null_ )); DESCR("encode text from DB encoding to ASCII text"); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 1bf67455e0..27d08810a9 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1893,6 +1893,9 @@ typedef struct WindowAggState int64 aggregatedbase; /* start row for current aggregates */ int64 aggregatedupto; /* rows before this one are aggregated */ + PGFunction startRangeFunc; /* Start comparator func, used for RANGE with values */ + PGFunction endRangeFunc; /* End comparator func, used for RANGE with values */ + bool sortByAsc; /* Sort By Asc, 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 */ @@ -1922,6 +1925,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 76a73b2a37..d23f02a809 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -499,22 +499,27 @@ 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_GROUP 0x080000 /* exclude group */ #define FRAMEOPTION_START_VALUE \ (FRAMEOPTION_START_VALUE_PRECEDING | FRAMEOPTION_START_VALUE_FOLLOWING) @@ -525,6 +530,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 +1284,8 @@ 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. + * startRangeFunc and/or endRangeFunc are 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,9 @@ typedef struct WindowClause char *refname; /* referenced window name, if any */ List *partitionClause; /* PARTITION BY list */ List *orderClause; /* ORDER BY list */ + Oid startRangeOid; /* Start comparator Oid, used for RANGE with values */ + Oid endRangeOid; /* End comparator Oid, used for RANGE with values */ + bool sortByAsc; /* Sort By Asc, 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 baf3c07417..32852eb0ca 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -808,6 +808,9 @@ 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 startRangeOid; /* Start comparator Oid, used for RANGE with values */ + Oid endRangeOid; /* End comparator Oid, used for RANGE with values */ + bool sortByAsc; /* Sort By Asc, 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 26af944e03..cf32197bc3 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 4e96fa7907..1abfe2400f 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/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 684f7f20a8..e3f39c02f9 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -717,6 +717,21 @@ macaddr8_le(macaddr8,macaddr8) macaddr8_gt(macaddr8,macaddr8) macaddr8_ge(macaddr8,macaddr8) macaddr8_ne(macaddr8,macaddr8) +in_range_int8_int8(bigint,bigint,bigint,boolean,boolean) +in_range_int8_int4(bigint,bigint,integer,boolean,boolean) +in_range_int8_int2(bigint,bigint,smallint,boolean,boolean) +in_range_int4_int8(integer,integer,bigint,boolean,boolean) +in_range_int4_int4(integer,integer,integer,boolean,boolean) +in_range_int4_int2(integer,integer,smallint,boolean,boolean) +in_range_int2_int8(smallint,smallint,bigint,boolean,boolean) +in_range_int2_int4(smallint,smallint,integer,boolean,boolean) +in_range_int2_int2(smallint,smallint,smallint,boolean,boolean) +in_range_timestamp_interval(timestamp without time zone,timestamp without time zone,interval,boolean,boolean) +in_range_timestamptz_interval(timestamp with time zone,timestamp with time zone,interval,boolean,boolean) +in_range_interval_interval(interval,interval,interval,boolean,boolean) +in_range_timetz_interval(time with time zone,time with time zone,interval,boolean,boolean) +in_range_date_interval(date,date,interval,boolean,boolean) +in_range_time_interval(time without time zone,time without time zone,interval,boolean,boolean) -- restore normal output mode \a\t -- List of functions used by libpq's fe-lobj.c diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 19f909f3d1..a8071ea8b3 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,176 @@ 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 first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + first_value | unique1 | four +-------------+---------+------ + 8 | 0 | 0 + 4 | 8 | 0 + 5 | 4 | 0 + 9 | 5 | 1 + 1 | 9 | 1 + 6 | 1 | 1 + 2 | 6 | 2 + 3 | 2 | 2 + 7 | 3 | 3 + | 7 | 3 +(10 rows) + +SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10; + first_value | unique1 | four +-------------+---------+------ + | 0 | 0 + 5 | 8 | 0 + 5 | 4 | 0 + | 5 | 1 + 6 | 9 | 1 + 6 | 1 | 1 + 3 | 6 | 2 + 3 | 2 | 2 + | 3 | 3 + | 7 | 3 +(10 rows) + +SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10; + first_value | unique1 | four +-------------+---------+------ + 0 | 0 | 0 + 8 | 8 | 0 + 4 | 4 | 0 + 5 | 5 | 1 + 9 | 9 | 1 + 1 | 1 | 1 + 6 | 6 | 2 + 2 | 2 | 2 + 3 | 3 | 3 + 7 | 7 | 3 +(10 rows) + +SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + last_value | unique1 | four +------------+---------+------ + 4 | 0 | 0 + 5 | 8 | 0 + 9 | 4 | 0 + 1 | 5 | 1 + 6 | 9 | 1 + 2 | 1 | 1 + 3 | 6 | 2 + 7 | 2 | 2 + 7 | 3 | 3 + | 7 | 3 +(10 rows) + +SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10; + last_value | unique1 | four +------------+---------+------ + | 0 | 0 + 5 | 8 | 0 + 9 | 4 | 0 + | 5 | 1 + 6 | 9 | 1 + 2 | 1 | 1 + 3 | 6 | 2 + 7 | 2 | 2 + | 3 | 3 + | 7 | 3 +(10 rows) + +SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10; + last_value | unique1 | four +------------+---------+------ + 0 | 0 | 0 + 5 | 8 | 0 + 9 | 4 | 0 + 5 | 5 | 1 + 6 | 9 | 1 + 2 | 1 | 1 + 3 | 6 | 2 + 7 | 2 | 2 + 3 | 3 | 3 + 7 | 7 | 3 +(10 rows) + SELECT sum(unique1) over (rows between 2 preceding and 1 preceding), unique1, four FROM tenk1 WHERE unique1 < 10; @@ -887,13 +1062,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 +1177,1701 @@ 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) 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) 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 desc range between 2::int8 preceding and 1::int2 preceding), + unique1, four +FROM tenk1 WHERE unique1 < 10; + sum | unique1 | four +-----+---------+------ + | 3 | 3 + | 7 | 3 + 10 | 6 | 2 + 10 | 2 | 2 + 18 | 9 | 1 + 18 | 5 | 1 + 18 | 1 | 1 + 23 | 0 | 0 + 23 | 8 | 0 + 23 | 4 | 0 +(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 desc range between '1 year'::interval preceding and '1 year'::interval following), + salary, enroll_date from empsalary; + sum | salary | enroll_date +-------+--------+------------- + 32200 | 4200 | 01-01-2008 + 32200 | 4500 | 01-01-2008 + 36100 | 3500 | 12-10-2007 + 47100 | 5200 | 08-15-2007 + 47100 | 4800 | 08-08-2007 + 47100 | 4800 | 08-01-2007 + 47100 | 5200 | 08-01-2007 + 38400 | 3900 | 12-23-2006 + 34900 | 5000 | 10-01-2006 + 34900 | 6000 | 10-01-2006 +(10 rows) + +select sum(salary) over (order by enroll_date desc range between '1 year'::interval following and '1 year'::interval following), + salary, enroll_date from empsalary; + sum | salary | enroll_date +-----+--------+------------- + | 4200 | 01-01-2008 + | 4500 | 01-01-2008 + | 3500 | 12-10-2007 + | 5200 | 08-15-2007 + | 4800 | 08-08-2007 + | 4800 | 08-01-2007 + | 5200 | 08-01-2007 + | 3900 | 12-23-2006 + | 5000 | 10-01-2006 + | 6000 | 10-01-2006 +(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 desc range between '1 hour'::interval preceding and '2 hours'::interval following), + salary, enroll_time from empsalary; + sum | salary | enroll_time +-------+--------+------------- + 16000 | 5200 | 20:00:00 + 20500 | 6000 | 19:00:00 + 15300 | 4800 | 18:00:00 + 17400 | 4500 | 17:00:00 + 16400 | 4200 | 15:00:00 + 16400 | 3900 | 15:00:00 + 21400 | 4800 | 14:00:00 + 18500 | 3500 | 13:00:00 + 13700 | 5000 | 12:00:00 + 10200 | 5200 | 11:00:00 +(10 rows) + +select sum(salary) over (order by enroll_time desc range between '1 hour'::interval following and '2 hours'::interval following), + salary, enroll_time from empsalary; + sum | salary | enroll_time +-------+--------+------------- + 10800 | 5200 | 20:00:00 + 9300 | 6000 | 19:00:00 + 4500 | 4800 | 18:00:00 + 8100 | 4500 | 17:00:00 + 8300 | 4200 | 15:00:00 + 8300 | 3900 | 15:00:00 + 8500 | 4800 | 14:00:00 + 10200 | 3500 | 13:00:00 + 5200 | 5000 | 12:00:00 + | 5200 | 11: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 desc range between '1 hour'::interval preceding and '2 hours'::interval following), + salary, enroll_timetz from empsalary; + sum | salary | enroll_timetz +-------+--------+--------------- + 16000 | 5200 | 20:00:00+01 + 20500 | 6000 | 19:00:00+01 + 15300 | 4800 | 18:00:00+01 + 17400 | 4500 | 17:00:00+01 + 16400 | 4200 | 15:00:00+01 + 16400 | 3900 | 15:00:00+01 + 21400 | 4800 | 14:00:00+01 + 18500 | 3500 | 13:00:00+01 + 13700 | 5000 | 12:00:00+01 + 10200 | 5200 | 11:00:00+01 +(10 rows) + +select sum(salary) over (order by enroll_timetz desc range between '1 hour'::interval following and '2 hours'::interval following), + salary, enroll_timetz from empsalary; + sum | salary | enroll_timetz +-------+--------+--------------- + 10800 | 5200 | 20:00:00+01 + 9300 | 6000 | 19:00:00+01 + 4500 | 4800 | 18:00:00+01 + 8100 | 4500 | 17:00:00+01 + 8300 | 4200 | 15:00:00+01 + 8300 | 3900 | 15:00:00+01 + 8500 | 4800 | 14:00:00+01 + 10200 | 3500 | 13:00:00+01 + 5200 | 5000 | 12:00:00+01 + | 5200 | 11: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 desc range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_interval from empsalary; + sum | salary | enroll_interval +-------+--------+----------------- + 16000 | 5200 | @ 10 years + 20500 | 6000 | @ 9 years + 15300 | 4800 | @ 8 years + 17400 | 4500 | @ 7 years + 16400 | 4200 | @ 5 years + 16400 | 3900 | @ 5 years + 21400 | 4800 | @ 4 years + 18500 | 3500 | @ 3 years + 13700 | 5000 | @ 2 years + 10200 | 5200 | @ 1 year +(10 rows) + +select sum(salary) over (order by enroll_interval desc range between '1 year'::interval following and '2 years'::interval following), + salary, enroll_interval from empsalary; + sum | salary | enroll_interval +-------+--------+----------------- + 10800 | 5200 | @ 10 years + 9300 | 6000 | @ 9 years + 4500 | 4800 | @ 8 years + 8100 | 4500 | @ 7 years + 8300 | 4200 | @ 5 years + 8300 | 3900 | @ 5 years + 8500 | 4800 | @ 4 years + 10200 | 3500 | @ 3 years + 5200 | 5000 | @ 2 years + | 5200 | @ 1 year +(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 desc range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_timestamptz from empsalary; + sum | salary | enroll_timestamptz +-------+--------+------------------------------ + 16000 | 5200 | Sun Oct 19 10:23:54 2008 PDT + 20500 | 6000 | Fri Oct 19 10:23:54 2007 PDT + 19500 | 4800 | Thu Oct 19 10:23:54 2006 PDT + 17400 | 4500 | Wed Oct 19 10:23:54 2005 PDT + 17400 | 4200 | Tue Oct 19 10:23:54 2004 PDT + 21400 | 3900 | Sun Oct 19 10:23:54 2003 PDT + 22400 | 4800 | Sat Oct 19 10:23:54 2002 PDT + 18500 | 3500 | Fri Oct 19 10:23:54 2001 PDT + 18500 | 5000 | Fri Oct 19 10:23:54 2001 PDT + 13700 | 5200 | Thu Oct 19 10:23:54 2000 PDT +(10 rows) + +select sum(salary) over (order by enroll_timestamptz desc range between '1 year'::interval following and '2 years'::interval following), + salary, enroll_timestamptz from empsalary; + sum | salary | enroll_timestamptz +-------+--------+------------------------------ + 10800 | 5200 | Sun Oct 19 10:23:54 2008 PDT + 9300 | 6000 | Fri Oct 19 10:23:54 2007 PDT + 8700 | 4800 | Thu Oct 19 10:23:54 2006 PDT + 8100 | 4500 | Wed Oct 19 10:23:54 2005 PDT + 8700 | 4200 | Tue Oct 19 10:23:54 2004 PDT + 13300 | 3900 | Sun Oct 19 10:23:54 2003 PDT + 13700 | 4800 | Sat Oct 19 10:23:54 2002 PDT + 5200 | 3500 | Fri Oct 19 10:23:54 2001 PDT + 5200 | 5000 | Fri Oct 19 10:23:54 2001 PDT + | 5200 | Thu Oct 19 10:23:54 2000 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 desc range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_timestamp from empsalary; + sum | salary | enroll_timestamp +-------+--------+-------------------------- + 16000 | 5200 | Sun Oct 19 10:23:54 2008 + 20500 | 6000 | Fri Oct 19 10:23:54 2007 + 19500 | 4800 | Thu Oct 19 10:23:54 2006 + 17400 | 4500 | Wed Oct 19 10:23:54 2005 + 17400 | 4200 | Tue Oct 19 10:23:54 2004 + 21400 | 3900 | Sun Oct 19 10:23:54 2003 + 22400 | 4800 | Sat Oct 19 10:23:54 2002 + 18500 | 3500 | Fri Oct 19 10:23:54 2001 + 18500 | 5000 | Fri Oct 19 10:23:54 2001 + 13700 | 5200 | Thu Oct 19 10:23:54 2000 +(10 rows) + +select sum(salary) over (order by enroll_timestamp desc range between '1 year'::interval following and '2 years'::interval following), + salary, enroll_timestamp from empsalary; + sum | salary | enroll_timestamp +-------+--------+-------------------------- + 10800 | 5200 | Sun Oct 19 10:23:54 2008 + 9300 | 6000 | Fri Oct 19 10:23:54 2007 + 8700 | 4800 | Thu Oct 19 10:23:54 2006 + 8100 | 4500 | Wed Oct 19 10:23:54 2005 + 8700 | 4200 | Tue Oct 19 10:23:54 2004 + 13300 | 3900 | Sun Oct 19 10:23:54 2003 + 13700 | 4800 | Sat Oct 19 10:23:54 2002 + 5200 | 3500 | Fri Oct 19 10:23:54 2001 + 5200 | 5000 | Fri Oct 19 10:23:54 2001 + | 5200 | Thu Oct 19 10:23:54 2000 +(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 + 6000 | 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) + +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 + exclude ties), + salary, enroll_date from empsalary; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 5000 | 5200 | 5000 | 10-01-2006 + 6000 | 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 | 4500 | 4500 | 01-01-2008 + 5000 | 4200 | 4200 | 01-01-2008 +(10 rows) + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + salary, enroll_date from empsalary; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 3900 | 5200 | 5000 | 10-01-2006 + 3900 | 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 | 3500 | 4500 | 01-01-2008 + 5000 | 3500 | 4200 | 01-01-2008 +(10 rows) + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + salary, enroll_date from empsalary; + first_value | last_value | salary | enroll_date +-------------+------------+--------+------------- + 6000 | 5200 | 5000 | 10-01-2006 + 5000 | 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 | 4500 | 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: no in_range function for given types +LINE 1: select sum(salary) over (order by depname range between '1 y... + ^ +select max(enroll_date) over (order by enroll_timestamp range between 1 preceding and 2 following + exclude ties), salary, enroll_timestamp from empsalary; +ERROR: no in_range function for given types +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: no in_range function for given types +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..359b01b312 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,46 @@ 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 first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT first_value(unique1) over (ORDER BY four rows between current row and 2 following exclude ties), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude current row), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT last_value(unique1) over (ORDER BY four rows between current row and 2 following exclude group), + unique1, four +FROM tenk1 WHERE unique1 < 10; + +SELECT last_value(unique1) over (ORDER BY four rows between current row 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 +250,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 +282,412 @@ 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 desc 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 desc range between '1 year'::interval preceding and '1 year'::interval following), + salary, enroll_date from empsalary; + +select sum(salary) over (order by enroll_date desc range between '1 year'::interval following 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 desc range between '1 hour'::interval preceding and '2 hours'::interval following), + salary, enroll_time from empsalary; + +select sum(salary) over (order by enroll_time desc range between '1 hour'::interval following 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 desc range between '1 hour'::interval preceding and '2 hours'::interval following), + salary, enroll_timetz from empsalary; + +select sum(salary) over (order by enroll_timetz desc range between '1 hour'::interval following 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 desc range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_interval from empsalary; + +select sum(salary) over (order by enroll_interval desc range between '1 year'::interval following 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 desc range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_timestamptz from empsalary; + +select sum(salary) over (order by enroll_timestamptz desc range between '1 year'::interval following 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 desc range between '1 year'::interval preceding and '2 years'::interval following), + salary, enroll_timestamp from empsalary; + +select sum(salary) over (order by enroll_timestamp desc range between '1 year'::interval following 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; + +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 + exclude ties), + salary, enroll_date from empsalary; + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude group), + salary, enroll_date from empsalary; + +select first_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + last_value(salary) over(order by enroll_date range between unbounded preceding and '1 year'::interval following + exclude current row), + 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;