diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 8a3e86b6db..40c146ca40 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 } frame_start [ frame_exclusion_clause ]
+{ RANGE | ROWS } BETWEEN frame_start AND frame_end [ frame_exclusion_clause ]
where frame_start and frame_end can be
@@ -874,6 +874,15 @@ CURRENT ROW
UNBOUNDED FOLLOWING
+ and the optional frame_exclusion_clause can be
+ one of
+
+
+EXCLUDE CURRENT ROW
+EXCLUDE TIES
+EXCLUDE NO OTHERS
+
+
If frame_end is omitted it defaults to CURRENT
ROW. Restrictions are that
frame_start cannot be UNBOUNDED FOLLOWING,
@@ -900,13 +909,25 @@ UNBOUNDED FOLLOWING
RANGE 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 or RANGE mode. In
+ ROWS mode, they indicate that the frame starts or ends with the row that
+ many rows before or after the current row. In RANGE mode, they indicate that
+ the frame starts or ends when the ORDER BY column's value for each row is within the bounds
+ specified by value for both the start and the end of the frame.
+ In ROWS mode, value must be an integer expression not
+ containing any variables, aggregate functions, or window functions.In RANGE mode,
+ there must be exactly one ORDER BY column and if the column is an integer column,
+ then value must be an integer. If it is a date/time column, then
+ value must be an interval. In both modes, the value must not be null or
+ negative; but it can be zero, which just selects the current row itself.
+
+
+
+ For the frame_exclusion_clause, EXCLUDE CURRENT ROW
+ excludes the current row from the frame. EXCLUDE TIES excludes any peers of the current row from the
+ frame. EXCLUDE NO OTHERS does nothing, but is provided in order to optionally document the intention
+ not to exclude any other rows.
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index a938a21334..93d0adcab8 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 } frame_start> [ frame_exclusion_clause ]
+{ RANGE | ROWS } BETWEEN frame_start> AND frame_end> [ frame_exclusion_clause ]
where frame_start and frame_end can be
one of
@@ -1817,6 +1817,12 @@ CURRENT ROW
value FOLLOWING
UNBOUNDED FOLLOWING
+ where the optional frame_exclusion_clause> can be one of
+
+EXCLUDE CURRENT ROW
+EXCLUDE TIES
+EXCLUDE NO OTHERS
+
@@ -1882,13 +1888,25 @@ UNBOUNDED FOLLOWING
The value PRECEDING and
- value FOLLOWING cases are currently only
- allowed in ROWS mode. They indicate that the frame starts
- or ends the specified number of rows before or after the current row.
- value must be an integer expression not
- containing any variables, aggregate functions, or window functions.
- The value must not be null or negative; but it can be zero, which
- just selects the current row.
+ value> FOLLOWING> cases, when used
+ in ROWS> mode, indicate that the frame starts or ends the specified
+ number of rows before or after the current row. In ROWS> mode,
+ value> must be an integer expression not containing any variables,
+ aggregate functions, or window functions.
+ When used in RANGE> mode, they indicate that the frame starts or ends when the value of
+ each row's ORDER BY column is within the start value and end value bounds. In RANGE> mode,
+ value> can be either an integer expression or a date/time interval.
+ In RANGE> mode, there must be exactly one ORDER BY column and if the column is an integer column,
+ then value> must be an integer.
+ If it is a date/time column, then value> must be an interval. In both modes,
+ the value must not be null or negative; but it can be zero, which just selects the current row.
+
+
+
+ For the frame_exclusion_clause, EXCLUDE CURRENT ROW>
+ excludes the current row from the frame. EXCLUDE TIES> excludes any peers of the current row from the
+ frame. EXCLUDE NO OTHERS> does nothing, but is provided in order to optionally document the intention
+ not to exclude any other rows.
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 02868749f6..4c6199fbe4 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -37,6 +37,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "executor/nodeWindowAgg.h"
#include "miscadmin.h"
@@ -180,7 +181,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_range(Oid sortColOid, Datum currval,
+ Datum slotval, Datum offset, bool preceding, bool end);
+static int row_is_in_frame(WindowAggState *winstate, int64 pos,
TupleTableSlot *slot);
static void update_frameheadpos(WindowObject winobj, TupleTableSlot *slot);
static void update_frametailpos(WindowObject winobj, TupleTableSlot *slot);
@@ -737,15 +740,16 @@ 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, 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->aggregatedbase <= winstate->currentpos &&
winstate->aggregatedupto > winstate->currentpos)
{
@@ -766,6 +770,8 @@ 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 EXCLUDE CURRENT ROW/EXCLUDE TIES mode, or
* - if the new frame doesn't overlap the old one
*
* Note that we don't strictly need to restart in the last case, but if
@@ -780,6 +786,8 @@ eval_windowaggregates(WindowAggState *winstate)
if (winstate->currentpos == 0 ||
(winstate->aggregatedbase != winstate->frameheadpos &&
!OidIsValid(peraggstate->invtransfn_oid)) ||
+ winstate->frameOptions & FRAMEOPTION_RANGE_BETWEEN ||
+ winstate->frameOptions & FRAMEOPTION_EXCLUSION ||
winstate->aggregatedupto <= winstate->frameheadpos)
{
peraggstate->restart = true;
@@ -920,6 +928,8 @@ eval_windowaggregates(WindowAggState *winstate)
*/
for (;;)
{
+ int ret;
+
/* Fetch next row if we didn't already */
if (TupIsNull(agg_row_slot))
{
@@ -928,9 +938,15 @@ eval_windowaggregates(WindowAggState *winstate)
break; /* must be end of partition */
}
- /* Exit loop (for now) if not in frame */
- if (!row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot))
+ /*
+ * Exit loop if no more rows can be in frame. Skip aggregation if current
+ * row is not in frame.
+ */
+ ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
+ if (ret == -1)
break;
+ else if (ret == 0)
+ goto next_tuple;
/* Set tuple context for evaluation of aggregate arguments */
winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
@@ -951,6 +967,7 @@ eval_windowaggregates(WindowAggState *winstate)
peraggstate);
}
+ next_tuple:
/* Reset per-input-tuple context after each tuple */
ResetExprContext(winstate->tmpcontext);
@@ -1113,7 +1130,9 @@ 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_EXCLUSION))
{
/* ... create a mark pointer to track the frame head */
agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0);
@@ -1268,6 +1287,136 @@ release_partition(WindowAggState *winstate)
}
/*
+ * row_is_in_range
+ * Determine whether a row is in range when in RANGE BETWEEN with values
+ * mode.
+ *
+ * Performs either an integer or a date/time comparison.
+ */
+static bool
+row_is_in_range(Oid sortColOid, Datum currval, Datum slotval, Datum offset,
+ bool preceding, bool end)
+{
+ Oid intOid = INT8OID;
+
+ /* If ORDER BY IS an int, perform int calculation */
+ if (can_coerce_type(1, &sortColOid, &intOid, COERCION_IMPLICIT))
+ {
+ int64 currint,
+ slotint,
+ offsetint;
+ currint = DatumGetInt64(currval);
+ slotint = DatumGetInt64(slotval);
+ offsetint = DatumGetInt64(offset);
+
+ /*
+ * SQL:2011 defines the range offsets as unsigned, so reject values
+ * less than 0.
+ */
+ if (offsetint < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("RANGE offsets cannot be negative. invalid value %zd", offsetint)));
+ if (preceding)
+ offsetint = -offsetint;
+
+ if (end)
+ {
+ if (slotint > currint + offsetint)
+ return false;
+ }
+ else
+ {
+ if (slotint < currint + offsetint)
+ return false;
+ }
+ }
+ else
+ {
+ /* ORDER BY is a date/time value, so perform interval calculation */
+ Datum val;
+ PGFunction intervalFunc,
+ comparatorFunc;
+
+ switch (sortColOid)
+ {
+ case TIMESTAMPOID:
+ if (preceding)
+ intervalFunc = timestamp_mi_interval;
+ else
+ intervalFunc = timestamp_pl_interval;
+ if (end)
+ comparatorFunc = timestamp_gt;
+ else
+ comparatorFunc = timestamp_lt;
+ break;
+ case TIMESTAMPTZOID:
+ if (preceding)
+ intervalFunc = timestamptz_mi_interval;
+ else
+ intervalFunc = timestamptz_pl_interval;
+ if (end)
+ comparatorFunc = timestamp_gt;
+ else
+ comparatorFunc = timestamp_lt;
+ break;
+ case INTERVALOID:
+ if (preceding)
+ intervalFunc = interval_mi;
+ else
+ intervalFunc = interval_pl;
+ if (end)
+ comparatorFunc = interval_gt;
+ else
+ comparatorFunc = interval_lt;
+ break;
+ case TIMETZOID:
+ if (preceding)
+ intervalFunc = timetz_mi_interval;
+ else
+ intervalFunc = timetz_pl_interval;
+ if (end)
+ comparatorFunc = timetz_gt;
+ else
+ comparatorFunc = timetz_lt;
+ break;
+ case DATEOID:
+ slotval = DirectFunctionCall1(date_timestamp, slotval);
+ if (preceding)
+ intervalFunc = date_mi_interval;
+ else
+ intervalFunc = date_pl_interval;
+ if (end)
+ comparatorFunc = timestamp_gt;
+ else
+ comparatorFunc = timestamp_lt;
+ break;
+ case TIMEOID:
+ currval = DirectFunctionCall1(time_interval, currval);
+ slotval = DirectFunctionCall1(time_interval, slotval);
+ if (preceding)
+ intervalFunc = interval_mi;
+ else
+ intervalFunc = interval_pl;
+ if (end)
+ comparatorFunc = interval_gt;
+ else
+ comparatorFunc = interval_lt;
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("ORDER BY column in window function must be an integral or date/time")));
+ }
+ val = DirectFunctionCall2(intervalFunc, currval, offset);
+ if (DirectFunctionCall2(comparatorFunc, slotval, val))
+ return false;
+ }
+
+ return true;
+}
+
+/*
* row_is_in_frame
* Determine whether a row is in the current row's window frame according
* to our window framing rule
@@ -1275,12 +1424,16 @@ release_partition(WindowAggState *winstate)
* The caller must have already determined that the row is in the partition
* and fetched it into a slot. This function just encapsulates the framing
* rules.
+ *
+ * Returns:
+ * -1, if the row is out of frame and no succeeding rows can be in frame
+ * 0, if the row is out of frame but succeeding rows might be in frame
+ * 1, if the row is in frame
*/
-static bool
+static int
row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
{
int frameOptions = winstate->frameOptions;
-
Assert(pos >= 0); /* else caller error */
/* First, check frame starting conditions */
@@ -1290,35 +1443,45 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
{
/* rows before current row are out of frame */
if (pos < winstate->currentpos)
- return false;
+ return -1;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* preceding row that is not peer is out of frame */
if (pos < winstate->currentpos &&
!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
- return false;
+ return -1;
}
else
Assert(false);
}
else if (frameOptions & FRAMEOPTION_START_VALUE)
{
+ int64 offset = DatumGetInt64(winstate->startOffsetValue);
+
if (frameOptions & FRAMEOPTION_ROWS)
{
- int64 offset = DatumGetInt64(winstate->startOffsetValue);
-
/* rows before current row + offset are out of frame */
if (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
offset = -offset;
-
if (pos < winstate->currentpos + offset)
- return false;
+ return 0;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
- /* parser should have rejected this */
- elog(ERROR, "window frame with value offset is not implemented");
+ bool isnull,
+ preceding;
+ Datum slotval,
+ currval;
+
+ slotval = slot_getattr(slot, 1, &isnull);
+ Assert(!isnull);
+ currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull);
+ Assert(!isnull);
+ preceding = (frameOptions & FRAMEOPTION_START_VALUE_PRECEDING) != 0 ? true : false;
+ if (!row_is_in_range(winstate->firstSortColOid, currval,
+ slotval, winstate->startOffsetValue, preceding, false))
+ return 0;
}
else
Assert(false);
@@ -1331,42 +1494,63 @@ row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
{
/* rows after current row are out of frame */
if (pos > winstate->currentpos)
- return false;
+ return -1;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
/* following row that is not peer is out of frame */
if (pos > winstate->currentpos &&
!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
- return false;
+ return -1;
}
else
Assert(false);
}
else if (frameOptions & FRAMEOPTION_END_VALUE)
{
+ int64 offset = DatumGetInt64(winstate->endOffsetValue);
if (frameOptions & FRAMEOPTION_ROWS)
{
- int64 offset = DatumGetInt64(winstate->endOffsetValue);
-
/* rows after current row + offset are out of frame */
if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
offset = -offset;
-
if (pos > winstate->currentpos + offset)
- return false;
+ return -1;
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
- /* parser should have rejected this */
- elog(ERROR, "window frame with value offset is not implemented");
+ bool isnull,
+ preceding;
+ Datum slotval,
+ currval;
+
+ slotval = slot_getattr(slot, 1, &isnull);
+ Assert(!isnull);
+ currval = slot_getattr(winstate->ss.ss_ScanTupleSlot, 1, &isnull);
+ Assert(!isnull);
+ preceding = (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING) != 0 ? true : false;
+ if (!row_is_in_range(winstate->firstSortColOid, currval,
+ slotval, winstate->endOffsetValue, preceding, true))
+ return -1;
}
else
Assert(false);
}
+ /* Check exclusion clause */
+ if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT)
+ {
+ if (pos == winstate->currentpos)
+ return 0;
+ } else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
+ {
+ if ((pos != winstate->currentpos) &&
+ are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
+ return 0;
+ }
+
/* If we get here, it's in frame */
- return true;
+ return 1;
}
/*
@@ -1464,8 +1648,8 @@ 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");
+ winstate->frameheadpos = 0;
+ winstate->framehead_valid = true;
}
else
Assert(false);
@@ -1545,14 +1729,12 @@ update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
}
else if (frameOptions & FRAMEOPTION_END_VALUE)
{
+ /* In ROWS mode, bound is physically n before/after current */
+ int64 offset = DatumGetInt64(winstate->endOffsetValue);
+ if (frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
+ offset = -offset;
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;
-
winstate->frametailpos = winstate->currentpos + offset;
/* smallest allowable value of frametailpos is -1 */
if (winstate->frametailpos < 0)
@@ -1568,8 +1750,8 @@ 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");
+ winstate->frametailpos = -1;
+ winstate->frametail_valid = true;
}
else
Assert(false);
@@ -1989,6 +2171,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
agg_winobj->readptr = -1;
winstate->agg_winobj = agg_winobj;
}
+ winstate->firstSortColOid = node->firstSortColOid;
/* copy frame options to state node for easy access */
winstate->frameOptions = node->frameOptions;
@@ -2736,7 +2919,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
WindowAggState *winstate;
ExprContext *econtext;
TupleTableSlot *slot;
- bool gottuple;
+ int gottuple;
int64 abs_pos;
Assert(WindowObjectIsValid(winobj));
@@ -2767,7 +2950,7 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
if (gottuple)
gottuple = row_is_in_frame(winstate, abs_pos, slot);
- if (!gottuple)
+ if (gottuple != 1)
{
if (isout)
*isout = true;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d9ff8a7e51..59cfe0c767 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1007,6 +1007,7 @@ _copyWindowAgg(const WindowAgg *from)
COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber));
COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
}
+ COPY_SCALAR_FIELD(firstSortColOid);
COPY_SCALAR_FIELD(frameOptions);
COPY_NODE_FIELD(startOffset);
COPY_NODE_FIELD(endOffset);
@@ -2405,6 +2406,7 @@ _copyWindowClause(const WindowClause *from)
COPY_STRING_FIELD(refname);
COPY_NODE_FIELD(partitionClause);
COPY_NODE_FIELD(orderClause);
+ COPY_SCALAR_FIELD(firstSortColOid);
COPY_SCALAR_FIELD(frameOptions);
COPY_NODE_FIELD(startOffset);
COPY_NODE_FIELD(endOffset);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2866fd7b4a..b0a453f958 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2720,6 +2720,7 @@ _equalWindowClause(const WindowClause *a, const WindowClause *b)
COMPARE_STRING_FIELD(refname);
COMPARE_NODE_FIELD(partitionClause);
COMPARE_NODE_FIELD(orderClause);
+ COMPARE_SCALAR_FIELD(firstSortColOid);
COMPARE_SCALAR_FIELD(frameOptions);
COMPARE_NODE_FIELD(startOffset);
COMPARE_NODE_FIELD(endOffset);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c97ee24ade..1cee88001d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -835,6 +835,7 @@ _outWindowAgg(StringInfo str, const WindowAgg *node)
for (i = 0; i < node->ordNumCols; i++)
appendStringInfo(str, " %u", node->ordOperators[i]);
+ WRITE_INT_FIELD(firstSortColOid);
WRITE_INT_FIELD(frameOptions);
WRITE_NODE_FIELD(startOffset);
WRITE_NODE_FIELD(endOffset);
@@ -2975,6 +2976,7 @@ _outWindowClause(StringInfo str, const WindowClause *node)
WRITE_STRING_FIELD(refname);
WRITE_NODE_FIELD(partitionClause);
WRITE_NODE_FIELD(orderClause);
+ WRITE_INT_FIELD(firstSortColOid);
WRITE_INT_FIELD(frameOptions);
WRITE_NODE_FIELD(startOffset);
WRITE_NODE_FIELD(endOffset);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 7eb67fc040..68ccbd6ff6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -366,6 +366,7 @@ _readWindowClause(void)
READ_STRING_FIELD(refname);
READ_NODE_FIELD(partitionClause);
READ_NODE_FIELD(orderClause);
+ READ_INT_FIELD(firstSortColOid);
READ_INT_FIELD(frameOptions);
READ_NODE_FIELD(startOffset);
READ_NODE_FIELD(endOffset);
@@ -2134,6 +2135,7 @@ _readWindowAgg(void)
READ_INT_FIELD(ordNumCols);
READ_ATTRNUMBER_ARRAY(ordColIdx, local_node->ordNumCols);
READ_OID_ARRAY(ordOperators, local_node->ordNumCols);
+ READ_INT_FIELD(firstSortColOid);
READ_INT_FIELD(frameOptions);
READ_NODE_FIELD(startOffset);
READ_NODE_FIELD(endOffset);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index d4454779ee..ca50ab8d43 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -259,8 +259,8 @@ static Material *make_material(Plan *lefttree);
static WindowAgg *make_windowagg(List *tlist, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
- int frameOptions, Node *startOffset, Node *endOffset,
- Plan *lefttree);
+ Oid firstSortColOid, int frameOptions, Node *startOffset,
+ Node *endOffset, Plan *lefttree);
static Group *make_group(List *tlist, List *qual, int numGroupCols,
AttrNumber *grpColIdx, Oid *grpOperators,
Plan *lefttree);
@@ -2114,6 +2114,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
ordNumCols,
ordColIdx,
ordOperators,
+ wc->firstSortColOid,
wc->frameOptions,
wc->startOffset,
wc->endOffset,
@@ -6059,8 +6060,8 @@ static WindowAgg *
make_windowagg(List *tlist, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
- int frameOptions, Node *startOffset, Node *endOffset,
- Plan *lefttree)
+ Oid firstSortColOid, int frameOptions, Node *startOffset,
+ Node *endOffset, Plan *lefttree)
{
WindowAgg *node = makeNode(WindowAgg);
Plan *plan = &node->plan;
@@ -6072,6 +6073,7 @@ make_windowagg(List *tlist, Index winref,
node->ordNumCols = ordNumCols;
node->ordColIdx = ordColIdx;
node->ordOperators = ordOperators;
+ node->firstSortColOid = firstSortColOid;
node->frameOptions = frameOptions;
node->startOffset = startOffset;
node->endOffset = endOffset;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c301ca465d..ebab53b8d5 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
@@ -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
@@ -13761,32 +13761,32 @@ 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.
+ * This is only a subset of the full SQL:2011 frame_clause grammar.
+ * We don't support GROUPS yet.
*/
opt_frame_clause:
- RANGE frame_extent
+ 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;
$$ = n;
}
| /*EMPTY*/
@@ -13903,6 +13903,26 @@ frame_bound:
}
;
+opt_window_exclusion_clause:
+ EXCLUDE CURRENT_P ROW
+ {
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_EXCLUDE_CURRENT;
+ $$ = n;
+ }
+ | EXCLUDE TIES
+ {
+ WindowDef *n = makeNode(WindowDef);
+ n->frameOptions = FRAMEOPTION_EXCLUDE_TIES;
+ $$ = n;
+ }
+ | EXCLUDE NO OTHERS
+ {
+ $$ = NULL;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
/*
* Supporting nonterminals for expressions.
@@ -14828,6 +14848,7 @@ unreserved_keyword:
| OPTION
| OPTIONS
| ORDINALITY
+ | OTHERS
| OVER
| OVERRIDING
| OWNED
@@ -14915,6 +14936,7 @@ unreserved_keyword:
| TEMPLATE
| TEMPORARY
| TEXT_P
+ | TIES
| TRANSACTION
| TRANSFORM
| TRIGGER
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 2828bbf796..73c80bbf1c 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -96,6 +96,8 @@ static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
static WindowClause *findWindowClause(List *wclist, const char *name);
static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
Node *clause);
+static inline void checkRangeBetweenOids(ParseState *pstate, WindowDef *windef,
+ Oid sortOid, Node *offset);
/*
@@ -2608,6 +2610,35 @@ transformSortClause(ParseState *pstate,
return sortlist;
}
+static inline void
+checkRangeBetweenOids(ParseState *pstate,
+ WindowDef *windef,
+ Oid sortOid,
+ Node *offset)
+{
+ Oid offsetOid = exprType(offset);
+ Oid intOid = INT8OID;
+ Oid intervalOid = INTERVALOID;
+
+ if (can_coerce_type(1, &sortOid, &intOid, COERCION_IMPLICIT))
+ {
+ if (!can_coerce_type(1, &offsetOid, &intOid, COERCION_IMPLICIT) ||
+ !can_coerce_type(1, &offsetOid, &intOid, COERCION_IMPLICIT))
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("Offsets must be an integral"),
+ parser_errposition(pstate, windef->location)));
+ }
+ else if (!can_coerce_type(1, &offsetOid, &intervalOid, COERCION_IMPLICIT) ||
+ !can_coerce_type(1, &offsetOid, &intervalOid, COERCION_IMPLICIT))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("Offsets must be an interval"),
+ parser_errposition(pstate, windef->location)));
+ }
+}
+
/*
* transformWindowDefinitions -
* transform window definitions (WindowDef to WindowClause)
@@ -2753,6 +2784,7 @@ transformWindowDefinitions(ParseState *pstate,
parser_errposition(pstate, windef->location)));
}
wc->frameOptions = windef->frameOptions;
+
/* Process frame offset expressions */
wc->startOffset = transformFrameOffset(pstate, wc->frameOptions,
windef->startOffset);
@@ -2760,9 +2792,36 @@ transformWindowDefinitions(ParseState *pstate,
windef->endOffset);
wc->winref = winref;
+ /* The RANGE BETWEEN clause requires exactly one ORDER BY column,
+ * of either an int or date/time type and start/end values to match by type.
+ */
+ if (wc->frameOptions & FRAMEOPTION_RANGE_BETWEEN)
+ {
+ SortBy *sortby;
+ TargetEntry *tle;
+ Oid sortOid;
+
+ if (list_length(orderClause) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("RANGE clause requires exactly one ORDER BY column"),
+ parser_errposition(pstate, windef->location)));
+ sortby = (SortBy *) lfirst(list_head(windef->orderClause));
+ tle = findTargetlistEntrySQL99(pstate, sortby->node,
+ targetlist, EXPR_KIND_WINDOW_ORDER);
+
+ /* Check that the sortOid and start/end offset Oids match */
+ sortOid = exprType((Node *) tle->expr);
+ if (wc->frameOptions & FRAMEOPTION_START_VALUE)
+ checkRangeBetweenOids(pstate, windef, sortOid, wc->startOffset);
+ if (wc->frameOptions & FRAMEOPTION_END_VALUE)
+ checkRangeBetweenOids(pstate, windef, sortOid, wc->endOffset);
+
+ wc->firstSortColOid = sortOid;
+ }
+
result = lappend(result, wc);
}
-
return result;
}
@@ -3513,16 +3572,21 @@ transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause)
}
else if (frameOptions & FRAMEOPTION_RANGE)
{
+ Oid nodeOid;
+ Oid intervalOid = INTERVALOID;
+
/* Transform the raw expression tree */
node = transformExpr(pstate, clause, EXPR_KIND_WINDOW_FRAME_RANGE);
/*
- * this needs a lot of thought to decide how to support in the context
- * of Postgres' extensible datatype framework
+ * Check that the specified type is an interval or integral
*/
constructName = "RANGE";
- /* error was already thrown by gram.y, this is just a backstop */
- elog(ERROR, "window frame with value offset is not implemented");
+ nodeOid = exprType(node);
+ if (can_coerce_type(1, &nodeOid, &intervalOid, COERCION_IMPLICIT))
+ node = coerce_to_specific_type(pstate, node, INTERVALOID, constructName);
+ else
+ node = coerce_to_specific_type(pstate, node, INT8OID, constructName);
}
else
{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e05bc04f52..2c5c2a7651 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1876,6 +1876,7 @@ typedef struct WindowAggState
int64 aggregatedbase; /* start row for current aggregates */
int64 aggregatedupto; /* rows before this one are aggregated */
+ Oid firstSortColOid; /* First ORDER BY Oid, used for RANGE with values */
int frameOptions; /* frame_clause options, see WindowDef */
ExprState *startOffset; /* expression for starting bound offset */
ExprState *endOffset; /* expression for ending bound offset */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 34d6afc80f..fab5a36c46 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -515,6 +515,9 @@ typedef struct WindowDef
#define FRAMEOPTION_END_VALUE_PRECEDING 0x00800 /* end is V. P. */
#define FRAMEOPTION_START_VALUE_FOLLOWING 0x01000 /* start is V. F. */
#define FRAMEOPTION_END_VALUE_FOLLOWING 0x02000 /* end is V. F. */
+#define FRAMEOPTION_RANGE_BETWEEN 0x04000 /* RANGE BETWEEN with values */
+#define FRAMEOPTION_EXCLUDE_CURRENT 0x08000 /* exclude current row */
+#define FRAMEOPTION_EXCLUDE_TIES 0x10000 /* exclude ties */
#define FRAMEOPTION_START_VALUE \
(FRAMEOPTION_START_VALUE_PRECEDING | FRAMEOPTION_START_VALUE_FOLLOWING)
@@ -525,6 +528,9 @@ typedef struct WindowDef
(FRAMEOPTION_RANGE | FRAMEOPTION_START_UNBOUNDED_PRECEDING | \
FRAMEOPTION_END_CURRENT_ROW)
+#define FRAMEOPTION_EXCLUSION \
+ (FRAMEOPTION_EXCLUDE_CURRENT | FRAMEOPTION_EXCLUDE_TIES)
+
/*
* RangeSubselect - subquery appearing in a FROM clause
*/
@@ -1275,6 +1281,7 @@ typedef struct GroupingSet
* if the clause originally came from WINDOW, and is NULL if it originally
* was an OVER clause (but note that we collapse out duplicate OVERs).
* partitionClause and orderClause are lists of SortGroupClause structs.
+ * firstSortColOid is set if the clause is a RANGE BETWEEN with values.
* winref is an ID number referenced by WindowFunc nodes; it must be unique
* among the members of a Query's windowClause list.
* When refname isn't null, the partitionClause is always copied from there;
@@ -1288,6 +1295,7 @@ typedef struct WindowClause
char *refname; /* referenced window name, if any */
List *partitionClause; /* PARTITION BY list */
List *orderClause; /* ORDER BY list */
+ Oid firstSortColOid; /* First ORDER BY Oid, used for RANGE with values */
int frameOptions; /* frame_clause options, see WindowDef */
Node *startOffset; /* expression for starting bound, if any */
Node *endOffset; /* expression for ending bound, if any */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 9b38d44ba0..d01828cae3 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -806,6 +806,7 @@ typedef struct WindowAgg
int ordNumCols; /* number of columns in ordering clause */
AttrNumber *ordColIdx; /* their indexes in the target list */
Oid *ordOperators; /* equality operators for ordering columns */
+ Oid firstSortColOid; /* First ORDER BY Oid, used for RANGE with values */
int frameOptions; /* frame_clause options, see WindowDef */
Node *startOffset; /* expression for starting bound, if any */
Node *endOffset; /* expression for ending bound, if any */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f50e45e886..39807675be 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -282,6 +282,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)
@@ -393,6 +394,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/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 19f909f3d1..981ca4e744 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,57 @@ 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 ties),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 4 | 4 | 0
+ 2 | 2 | 2
+ 1 | 1 | 1
+ 6 | 6 | 2
+ 9 | 9 | 1
+ 8 | 8 | 0
+ 5 | 5 | 1
+ 3 | 3 | 3
+ 7 | 7 | 3
+ 0 | 0 | 0
+(10 rows)
+
SELECT sum(unique1) over (rows between 2 preceding and 1 preceding),
unique1, four
FROM tenk1 WHERE unique1 < 10;
@@ -887,13 +943,40 @@ 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 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
@@ -951,13 +1034,531 @@ SELECT * FROM v_window;
(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+
+ pg_get_viewdef
+----------------------------------------------
+ SELECT i.i, +
+ sum(i.i) OVER (ORDER BY i.i) AS sum_rows+
FROM generate_series(1, 10) i(i);
(1 row)
+-- RANGE BETWEEN with values tests
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ | 0 | 0
+ | 8 | 0
+ | 4 | 0
+ 12 | 5 | 1
+ 12 | 9 | 1
+ 12 | 1 | 1
+ 27 | 6 | 2
+ 27 | 2 | 2
+ 23 | 3 | 3
+ 23 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude 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 (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 4 | 0 | 0
+ 12 | 4 | 0
+ 12 | 8 | 0
+ 6 | 1 | 1
+ 15 | 5 | 1
+ 14 | 9 | 1
+ 8 | 2 | 2
+ 8 | 6 | 2
+ 10 | 3 | 3
+ 10 | 7 | 3
+(10 rows)
+
+SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following
+ exclude current row),unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four
+-----+---------+------
+ 4 | 0 | 0
+ 8 | 4 | 0
+ 4 | 8 | 0
+ 5 | 1 | 1
+ 10 | 5 | 1
+ 5 | 9 | 1
+ 6 | 2 | 2
+ 2 | 6 | 2
+ 7 | 3 | 3
+ 3 | 7 | 3
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following),
+ salary, enroll_date from empsalary;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 34900 | 5000 | 10-01-2006
+ 34900 | 6000 | 10-01-2006
+ 38400 | 3900 | 12-23-2006
+ 47100 | 4800 | 08-01-2007
+ 47100 | 5200 | 08-01-2007
+ 47100 | 4800 | 08-08-2007
+ 47100 | 5200 | 08-15-2007
+ 36100 | 3500 | 12-10-2007
+ 32200 | 4500 | 01-01-2008
+ 32200 | 4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+ exclude current row), salary, enroll_date from empsalary;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 29900 | 5000 | 10-01-2006
+ 28900 | 6000 | 10-01-2006
+ 34500 | 3900 | 12-23-2006
+ 42300 | 4800 | 08-01-2007
+ 41900 | 5200 | 08-01-2007
+ 42300 | 4800 | 08-08-2007
+ 41900 | 5200 | 08-15-2007
+ 32600 | 3500 | 12-10-2007
+ 27700 | 4500 | 01-01-2008
+ 28000 | 4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+ exclude ties), salary, enroll_date from empsalary;
+ sum | salary | enroll_date
+-------+--------+-------------
+ 28900 | 5000 | 10-01-2006
+ 29900 | 6000 | 10-01-2006
+ 38400 | 3900 | 12-23-2006
+ 41900 | 4800 | 08-01-2007
+ 42300 | 5200 | 08-01-2007
+ 47100 | 4800 | 08-08-2007
+ 47100 | 5200 | 08-15-2007
+ 36100 | 3500 | 12-10-2007
+ 28000 | 4500 | 01-01-2008
+ 27700 | 4200 | 01-01-2008
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following),
+ salary, enroll_time from empsalary;
+ sum | salary | enroll_time
+-------+--------+-------------
+ 13700 | 5200 | 11:00:00
+ 18500 | 5000 | 12:00:00
+ 21400 | 3500 | 13:00:00
+ 16400 | 4800 | 14:00:00
+ 17400 | 3900 | 15:00:00
+ 17400 | 4200 | 15:00:00
+ 15300 | 4500 | 17:00:00
+ 20500 | 4800 | 18:00:00
+ 16000 | 6000 | 19:00:00
+ 11200 | 5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude current row), salary, enroll_time from empsalary;
+ sum | salary | enroll_time
+-------+--------+-------------
+ 8500 | 5200 | 11:00:00
+ 13500 | 5000 | 12:00:00
+ 17900 | 3500 | 13:00:00
+ 11600 | 4800 | 14:00:00
+ 13500 | 3900 | 15:00:00
+ 13200 | 4200 | 15:00:00
+ 10800 | 4500 | 17:00:00
+ 15700 | 4800 | 18:00:00
+ 10000 | 6000 | 19:00:00
+ 6000 | 5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude ties), salary, enroll_time from empsalary;
+ sum | salary | enroll_time
+-------+--------+-------------
+ 13700 | 5200 | 11:00:00
+ 18500 | 5000 | 12:00:00
+ 21400 | 3500 | 13:00:00
+ 16400 | 4800 | 14:00:00
+ 13200 | 3900 | 15:00:00
+ 13500 | 4200 | 15:00:00
+ 15300 | 4500 | 17:00:00
+ 20500 | 4800 | 18:00:00
+ 16000 | 6000 | 19:00:00
+ 11200 | 5200 | 20:00:00
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following),
+ salary, enroll_timetz from empsalary;
+ sum | salary | enroll_timetz
+-------+--------+---------------
+ 13700 | 5200 | 11:00:00+01
+ 18500 | 5000 | 12:00:00+01
+ 21400 | 3500 | 13:00:00+01
+ 16400 | 4800 | 14:00:00+01
+ 17400 | 3900 | 15:00:00+01
+ 17400 | 4200 | 15:00:00+01
+ 15300 | 4500 | 17:00:00+01
+ 20500 | 4800 | 18:00:00+01
+ 16000 | 6000 | 19:00:00+01
+ 11200 | 5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude current row), salary, enroll_timetz from empsalary;
+ sum | salary | enroll_timetz
+-------+--------+---------------
+ 8500 | 5200 | 11:00:00+01
+ 13500 | 5000 | 12:00:00+01
+ 17900 | 3500 | 13:00:00+01
+ 11600 | 4800 | 14:00:00+01
+ 13500 | 3900 | 15:00:00+01
+ 13200 | 4200 | 15:00:00+01
+ 10800 | 4500 | 17:00:00+01
+ 15700 | 4800 | 18:00:00+01
+ 10000 | 6000 | 19:00:00+01
+ 6000 | 5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude ties), salary, enroll_timetz from empsalary;
+ sum | salary | enroll_timetz
+-------+--------+---------------
+ 13700 | 5200 | 11:00:00+01
+ 18500 | 5000 | 12:00:00+01
+ 21400 | 3500 | 13:00:00+01
+ 16400 | 4800 | 14:00:00+01
+ 13200 | 3900 | 15:00:00+01
+ 13500 | 4200 | 15:00:00+01
+ 15300 | 4500 | 17:00:00+01
+ 20500 | 4800 | 18:00:00+01
+ 16000 | 6000 | 19:00:00+01
+ 11200 | 5200 | 20:00:00+01
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following),
+ salary, enroll_interval from empsalary;
+ sum | salary | enroll_interval
+-------+--------+-----------------
+ 13700 | 5200 | @ 1 year
+ 18500 | 5000 | @ 2 years
+ 21400 | 3500 | @ 3 years
+ 16400 | 4800 | @ 4 years
+ 17400 | 3900 | @ 5 years
+ 17400 | 4200 | @ 5 years
+ 15300 | 4500 | @ 7 years
+ 20500 | 4800 | @ 8 years
+ 16000 | 6000 | @ 9 years
+ 11200 | 5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+ exclude current row), salary, enroll_interval from empsalary;
+ sum | salary | enroll_interval
+-------+--------+-----------------
+ 8500 | 5200 | @ 1 year
+ 13500 | 5000 | @ 2 years
+ 17900 | 3500 | @ 3 years
+ 11600 | 4800 | @ 4 years
+ 13500 | 3900 | @ 5 years
+ 13200 | 4200 | @ 5 years
+ 10800 | 4500 | @ 7 years
+ 15700 | 4800 | @ 8 years
+ 10000 | 6000 | @ 9 years
+ 6000 | 5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_interval from empsalary;
+ sum | salary | enroll_interval
+-------+--------+-----------------
+ 13700 | 5200 | @ 1 year
+ 18500 | 5000 | @ 2 years
+ 21400 | 3500 | @ 3 years
+ 16400 | 4800 | @ 4 years
+ 13200 | 3900 | @ 5 years
+ 13500 | 4200 | @ 5 years
+ 15300 | 4500 | @ 7 years
+ 20500 | 4800 | @ 8 years
+ 16000 | 6000 | @ 9 years
+ 11200 | 5200 | @ 10 years
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following),
+ salary, enroll_timestamptz from empsalary;
+ sum | salary | enroll_timestamptz
+-------+--------+------------------------------
+ 18500 | 5200 | Thu Oct 19 10:23:54 2000 PDT
+ 22400 | 5000 | Fri Oct 19 10:23:54 2001 PDT
+ 22400 | 3500 | Fri Oct 19 10:23:54 2001 PDT
+ 21400 | 4800 | Sat Oct 19 10:23:54 2002 PDT
+ 17400 | 3900 | Sun Oct 19 10:23:54 2003 PDT
+ 17400 | 4200 | Tue Oct 19 10:23:54 2004 PDT
+ 19500 | 4500 | Wed Oct 19 10:23:54 2005 PDT
+ 20500 | 4800 | Thu Oct 19 10:23:54 2006 PDT
+ 16000 | 6000 | Fri Oct 19 10:23:54 2007 PDT
+ 11200 | 5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+ exclude current row), salary, enroll_timestamptz from empsalary;
+ sum | salary | enroll_timestamptz
+-------+--------+------------------------------
+ 13300 | 5200 | Thu Oct 19 10:23:54 2000 PDT
+ 17400 | 5000 | Fri Oct 19 10:23:54 2001 PDT
+ 18900 | 3500 | Fri Oct 19 10:23:54 2001 PDT
+ 16600 | 4800 | Sat Oct 19 10:23:54 2002 PDT
+ 13500 | 3900 | Sun Oct 19 10:23:54 2003 PDT
+ 13200 | 4200 | Tue Oct 19 10:23:54 2004 PDT
+ 15000 | 4500 | Wed Oct 19 10:23:54 2005 PDT
+ 15700 | 4800 | Thu Oct 19 10:23:54 2006 PDT
+ 10000 | 6000 | Fri Oct 19 10:23:54 2007 PDT
+ 6000 | 5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_timestamptz from empsalary;
+ sum | salary | enroll_timestamptz
+-------+--------+------------------------------
+ 18500 | 5200 | Thu Oct 19 10:23:54 2000 PDT
+ 18900 | 5000 | Fri Oct 19 10:23:54 2001 PDT
+ 17400 | 3500 | Fri Oct 19 10:23:54 2001 PDT
+ 21400 | 4800 | Sat Oct 19 10:23:54 2002 PDT
+ 17400 | 3900 | Sun Oct 19 10:23:54 2003 PDT
+ 17400 | 4200 | Tue Oct 19 10:23:54 2004 PDT
+ 19500 | 4500 | Wed Oct 19 10:23:54 2005 PDT
+ 20500 | 4800 | Thu Oct 19 10:23:54 2006 PDT
+ 16000 | 6000 | Fri Oct 19 10:23:54 2007 PDT
+ 11200 | 5200 | Sun Oct 19 10:23:54 2008 PDT
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following),
+ salary, enroll_timestamp from empsalary;
+ sum | salary | enroll_timestamp
+-------+--------+--------------------------
+ 18500 | 5200 | Thu Oct 19 10:23:54 2000
+ 22400 | 5000 | Fri Oct 19 10:23:54 2001
+ 22400 | 3500 | Fri Oct 19 10:23:54 2001
+ 21400 | 4800 | Sat Oct 19 10:23:54 2002
+ 17400 | 3900 | Sun Oct 19 10:23:54 2003
+ 17400 | 4200 | Tue Oct 19 10:23:54 2004
+ 19500 | 4500 | Wed Oct 19 10:23:54 2005
+ 20500 | 4800 | Thu Oct 19 10:23:54 2006
+ 16000 | 6000 | Fri Oct 19 10:23:54 2007
+ 11200 | 5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+ exclude current row), salary, enroll_timestamp from empsalary;
+ sum | salary | enroll_timestamp
+-------+--------+--------------------------
+ 13300 | 5200 | Thu Oct 19 10:23:54 2000
+ 17400 | 5000 | Fri Oct 19 10:23:54 2001
+ 18900 | 3500 | Fri Oct 19 10:23:54 2001
+ 16600 | 4800 | Sat Oct 19 10:23:54 2002
+ 13500 | 3900 | Sun Oct 19 10:23:54 2003
+ 13200 | 4200 | Tue Oct 19 10:23:54 2004
+ 15000 | 4500 | Wed Oct 19 10:23:54 2005
+ 15700 | 4800 | Thu Oct 19 10:23:54 2006
+ 10000 | 6000 | Fri Oct 19 10:23:54 2007
+ 6000 | 5200 | Sun Oct 19 10:23:54 2008
+(10 rows)
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+ exclude 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 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)
+
+-- RANGE BETWEEN with values negative tests
+select sum(salary) over (order by enroll_timestamp, enroll_date range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_timestamp from empsalary;
+ERROR: RANGE clause requires exactly one ORDER BY column
+LINE 1: select sum(salary) over (order by enroll_timestamp, enroll_d...
+ ^
+select sum(salary) over (range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_timestamp from empsalary;
+ERROR: RANGE clause requires exactly one ORDER BY column
+LINE 1: select sum(salary) over (range between '1 year'::interval pr...
+ ^
+select sum(salary) over (order by depname range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_timestamp from empsalary;
+ERROR: ORDER BY column in window function must be an integral or date/time
+select max(enroll_date) over (order by enroll_timestamp range between 1 preceding and 2 following
+ exclude ties), salary, enroll_timestamp from empsalary;
+ERROR: Offsets must be an interval
+LINE 1: select max(enroll_date) over (order by enroll_timestamp rang...
+ ^
+select max(enroll_date) over (order by salary range between -1 preceding and 2 following
+ exclude ties), salary, enroll_timestamp from empsalary;
+ERROR: RANGE offsets cannot be negative. invalid value -1
+select max(enroll_date) over (order by salary range between 1 preceding and -2 following
+ exclude ties), salary, enroll_timestamp from empsalary;
+ERROR: RANGE offsets cannot be negative. invalid value -2
+select max(enroll_date) over (order by salary range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_timestamp from empsalary;
+ERROR: Offsets must be an integral
+LINE 1: select max(enroll_date) over (order by salary range between ...
+ ^
-- 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..5449d5f62e 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,18 @@ 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 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 +222,13 @@ 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 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 +250,126 @@ SELECT * FROM v_window;
SELECT pg_get_viewdef('v_window');
+-- RANGE BETWEEN with values tests
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude no others),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude current row),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding exclude 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 (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following),
+ unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (partition by four order by unique1 range between 5::int8 preceding and 6::int2 following
+ exclude current row),unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following),
+ salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+ exclude current row), salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_date range between '1 year'::interval preceding and '1 year'::interval following
+ exclude ties), salary, enroll_date from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following),
+ salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude current row), salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_time range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude ties), salary, enroll_time from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following),
+ salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude current row), salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_timetz range between '1 hour'::interval preceding and '2 hours'::interval following
+ exclude ties), salary, enroll_timetz from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following),
+ salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+ exclude current row), salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_interval range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_interval from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following),
+ salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+ exclude current row), salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamptz range between '1 year'::interval preceding and '2 years'::interval following
+ exclude ties), salary, enroll_timestamptz from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following),
+ salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+ exclude current row), salary, enroll_timestamp from empsalary;
+
+select sum(salary) over (order by enroll_timestamp range between '1 year'::interval preceding and '2 years'::interval following
+ exclude 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 ties), 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;
+
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;