-<!-- $PostgreSQL: pgsql/doc/src/sgml/advanced.sgml,v 1.55 2008/12/28 18:53:53 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/advanced.sgml,v 1.56 2008/12/31 00:08:32 tgl Exp $ -->
<chapter id="tutorial-advanced">
<title>Advanced Features</title>
<para>
We already saw that <literal>ORDER BY</> can be omitted if the ordering
of rows is not important. It is also possible to omit <literal>PARTITION
- BY</>, in which case the window function is computed over all rows of the
- virtual table; that is, there is one partition containing all the rows.
+ BY</>, in which case there is just one partition containing all the rows.
</para>
<para>
There is another important concept associated with window functions:
for each row, there is a set of rows within its partition called its
- <firstterm>window frame</>. When <literal>ORDER BY</> is omitted the
- frame is always the same as the partition. If <literal>ORDER BY</> is
- supplied, the frame consists of all rows from the start of the partition
- up to the current row, plus any following rows that are equal to the
- current row according to the <literal>ORDER BY</> clause.
+ <firstterm>window frame</>. Many (but not all) window functions act only
+ on the rows of the window frame, rather than of the whole partition.
+ By default, if <literal>ORDER BY</> is supplied then the frame consists of
+ all rows from the start of the partition up through the current row, plus
+ any following rows that are equal to the current row according to the
+ <literal>ORDER BY</> clause. When <literal>ORDER BY</> is omitted the
+ default frame consists of all rows in the partition.
<footnote>
<para>
- The SQL standard includes options to define the window frame in
- other ways, but this definition is the only one currently supported
- by <productname>PostgreSQL</productname>.
+ There are options to define the window frame in other ways, but
+ this tutorial does not cover them. See
+ <xref linkend="syntax-window-functions"> for details.
</para>
</footnote>
- Many window functions act only on the rows of the window frame, rather
- than of the whole partition. Here is an example using <function>sum</>:
+ Here is an example using <function>sum</>:
</para>
<programlisting>
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
</programlisting>
</para>
+
+ <para>
+ More details about window functions can be found in
+ <xref linkend="syntax-window-functions">,
+ <xref linkend="queries-window">, and the
+ <xref linkend="sql-select" endterm="sql-select-title"> reference page.
+ </para>
</sect1>
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.464 2008/12/28 18:53:53 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.465 2008/12/31 00:08:33 tgl Exp $ -->
<chapter id="functions">
<title>Functions and Operators</title>
<para>
<firstterm>Window functions</firstterm> provide the ability to perform
calculations across sets of rows that are related to the current query
- row. For information about this feature see
- <xref linkend="tutorial-window"> and
- <xref linkend="syntax-window-functions">.
+ row. See <xref linkend="tutorial-window"> for an introduction to this
+ feature.
</para>
<para>
<para>
Note that <function>first_value</>, <function>last_value</>, and
<function>nth_value</> consider only the rows within the <quote>window
- frame</>, that is the rows from the start of the partition through the
- last peer of the current row. This is particularly likely to give
- unintuitive results for <function>last_value</>.
+ frame</>, which by default contains the rows from the start of the
+ partition through the last peer of the current row. This is
+ likely to give unhelpful results for <function>nth_value</> and
+ particularly <function>last_value</>. You can redefine the frame as
+ being the whole partition by adding <literal>ROWS BETWEEN UNBOUNDED
+ PRECEDING AND UNBOUNDED FOLLOWING</> to the <literal>OVER</> clause.
+ See <xref linkend="syntax-window-functions"> for more information.
</para>
<para>
When an aggregate function is used as a window function, it aggregates
over the rows within the current row's window frame. To obtain
- aggregation over the whole partition, be sure to omit <literal>ORDER BY</>
- from the window definition. An aggregate used with <literal>ORDER BY</>
- produces a <quote>running sum</> type of behavior, which may or may not
- be what's wanted.
+ aggregation over the whole partition, omit <literal>ORDER BY</> or use
+ <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>.
+ An aggregate used with <literal>ORDER BY</> and the default window frame
+ definition produces a <quote>running sum</> type of behavior, which may or
+ may not be what's wanted.
</para>
<note>
same as the standard's default, namely <literal>RESPECT NULLS</>.
Likewise, the standard's <literal>FROM FIRST</> or <literal>FROM LAST</>
option for <function>nth_value</> is not implemented: only the
- default <literal>FROM FIRST</> behavior is supported.
+ default <literal>FROM FIRST</> behavior is supported. (You can achieve
+ the result of <literal>FROM LAST</> by reversing the <literal>ORDER BY</>
+ ordering.)
</para>
</note>
-<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.51 2008/12/28 18:53:54 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.52 2008/12/31 00:08:35 tgl Exp $ -->
<chapter id="queries">
<title>Queries</title>
</para>
<para>
- Currently, use of window functions always forces sorting, and so the
+ Currently, window functions always require presorted data, and so the
query output will be ordered according to one or another of the window
functions' <literal>PARTITION BY</>/<literal>ORDER BY</> clauses.
It is not recommendable to rely on this, however. Use an explicit
<!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.114 2008/12/29 18:23:53 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.115 2008/12/31 00:08:35 tgl Exp $
PostgreSQL documentation
-->
[ <replaceable class="parameter">existing_window_name</replaceable> ]
[ PARTITION BY <replaceable class="parameter">expression</replaceable> [, ...] ]
[ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
+[ <replaceable class="parameter">framing_clause</replaceable> ]
</synopsis>
+ </para>
+
+ <para>
+ If an <replaceable class="parameter">existing_window_name</replaceable>
+ is specified it must refer to an earlier entry in the <literal>WINDOW</>
+ list; the new window copies its partitioning clause from that entry,
+ as well as its ordering clause if any. In this case the new window cannot
+ specify its own <literal>PARTITION BY</> clause, and it can specify
+ <literal>ORDER BY</> only if the copied window does not have one.
+ The framing clause is never copied from the existing window.
+ </para>
+
+ <para>
The elements of the <literal>PARTITION BY</> list are interpreted in
the same fashion as elements of a
<xref linkend="sql-groupby" endterm="sql-groupby-title">, and
</para>
<para>
- If an <replaceable class="parameter">existing_window_name</replaceable>
- is specified it must refer to an earlier entry in the <literal>WINDOW</>
- list; the new window copies its partitioning clause from that entry,
- as well as its ordering clause if any. In this case the new window cannot
- specify its own <literal>PARTITION BY</> clause, and it can specify
- <literal>ORDER BY</> only if the copied window does not have one.
+ The optional <replaceable class="parameter">framing_clause</> defines
+ the <firstterm>window frame</> for window functions that depend on the
+ frame (not all do). It can be one of
+<synopsis>
+RANGE UNBOUNDED PRECEDING
+RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ROWS UNBOUNDED PRECEDING
+ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+</synopsis>
+ The first two are equivalent and are also the default: they set the
+ frame to be all rows from the partition start up through the current row's
+ last peer in the <literal>ORDER BY</> ordering (which means all rows if
+ there is no <literal>ORDER BY</>). The options
+ <literal>RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</> and
+ <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>
+ are also equivalent: they always select all rows in the partition.
+ Lastly, <literal>ROWS UNBOUNDED PRECEDING</> or its verbose equivalent
+ <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</> select
+ all rows up through the current row (regardless of duplicates).
+ Beware that this option can produce implementation-dependent results
+ if the <literal>ORDER BY</> ordering does not order the rows uniquely.
</para>
<para>
<para>
Window functions are described in detail in
- <xref linkend="tutorial-window"> and
- <xref linkend="syntax-window-functions">.
+ <xref linkend="tutorial-window">,
+ <xref linkend="syntax-window-functions">, and
+ <xref linkend="queries-window">.
</para>
</refsect2>
<title><literal>WINDOW</literal> Clause Restrictions</title>
<para>
- The SQL standard provides for an optional <quote>framing clause</>,
- introduced by the key word <literal>RANGE</> or <literal>ROWS</>,
- in window definitions. <productname>PostgreSQL</productname> does
- not yet implement framing clauses, and always follows the
- default framing behavior, which is equivalent to the framing clause
- <literal>RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</>.
+ The SQL standard provides additional options for the window
+ <replaceable class="parameter">framing_clause</>.
+ <productname>PostgreSQL</productname> currently supports only the
+ options listed above.
</para>
</refsect2>
-<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.127 2008/12/28 18:53:54 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.128 2008/12/31 00:08:35 tgl Exp $ -->
<chapter id="sql-syntax">
<title>SQL Syntax</title>
[ <replaceable class="parameter">window_name</replaceable> ]
[ PARTITION BY <replaceable class="parameter">expression</replaceable> [, ...] ]
[ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
+[ <replaceable class="parameter">framing_clause</replaceable> ]
+</synopsis>
+ and the optional <replaceable class="parameter">framing_clause</replaceable>
+ can be one of
+<synopsis>
+RANGE UNBOUNDED PRECEDING
+RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ROWS UNBOUNDED PRECEDING
+ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
</synopsis>
Here, <replaceable>expression</replaceable> represents any value
Named window specifications are usually referenced with just
<literal>OVER</> <replaceable>window_name</replaceable>, but it is
also possible to write a window name inside the parentheses and then
- optionally override its ordering clause with <literal>ORDER BY</>.
+ optionally override its ordering clause and/or framing clause.
This latter syntax follows the same rules as modifying an existing
window name within the <literal>WINDOW</literal> clause; see the
<xref linkend="sql-select" endterm="sql-select-title"> reference
page for details.
</para>
+ <para>
+ The default framing option is <literal>RANGE UNBOUNDED PRECEDING</>,
+ which is the same as <literal>RANGE BETWEEN UNBOUNDED PRECEDING AND
+ CURRENT ROW</>; it selects rows up through the current row's last
+ peer in the <literal>ORDER BY</> ordering (which means all rows if
+ there is no <literal>ORDER BY</>). The options
+ <literal>RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</> and
+ <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING</>
+ are also equivalent: they always select all rows in the partition.
+ Lastly, <literal>ROWS UNBOUNDED PRECEDING</> or its verbose equivalent
+ <literal>ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</> select
+ all rows up through the current row (regardless of duplicates).
+ Beware that this option can produce implementation-dependent results
+ if the <literal>ORDER BY</> ordering does not order the rows uniquely.
+ </para>
+
<para>
The built-in window functions are described in <xref
linkend="functions-window-table">. Also, any built-in or
user-defined aggregate function can be used as a window function.
- Currently, there is no provision for user-defined window functions
- other than aggregates.
</para>
<para>
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/nodeWindowAgg.c,v 1.1 2008/12/28 18:53:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/nodeWindowAgg.c,v 1.2 2008/12/31 00:08:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
bool initValueIsNull;
/*
- * cached value for non-moving frame
+ * cached value for current frame boundaries
*/
Datum resultValue;
bool resultValueIsNull;
- bool hasResult;
/*
* We need the len and byval info for the agg's input, result, and
static void spool_tuples(WindowAggState *winstate, int64 pos);
static void release_partition(WindowAggState *winstate);
+static bool row_is_in_frame(WindowAggState *winstate, int64 pos,
+ TupleTableSlot *slot);
+static void update_frametailpos(WindowObject winobj, TupleTableSlot *slot);
+
static WindowStatePerAggData *initialize_peragg(WindowAggState *winstate,
WindowFunc *wfunc,
WindowStatePerAgg peraggstate);
}
peraggstate->transValueIsNull = peraggstate->initValueIsNull;
peraggstate->noTransValue = peraggstate->initValueIsNull;
+ peraggstate->resultValueIsNull = true;
}
/*
int i;
MemoryContext oldContext;
ExprContext *econtext;
- TupleTableSlot *first_peer_slot = winstate->first_peer_slot;
- TupleTableSlot *slot;
- bool first;
+ TupleTableSlot *agg_row_slot;
numaggs = winstate->numaggs;
if (numaggs == 0)
econtext = winstate->ss.ps.ps_ExprContext;
/*
- * We don't currently support explicitly-specified window frames. That
- * means that the window frame always includes all the rows in the
- * partition preceding and including the current row, and all its
- * peers. As a special case, if there's no ORDER BY, all rows are peers,
- * so the window frame includes all rows in the partition.
+ * Currently, we support only a subset of the SQL-standard window framing
+ * rules. In all the supported cases, the window frame always 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 row advances forward. This makes it possible to use an
+ * incremental strategy for evaluating aggregates: we run the transition
+ * function for each row added to the frame, and run the final function
+ * whenever we need the current aggregate value. This is considerably
+ * more efficient than the naive approach of re-running the entire
+ * aggregate calculation for each current row. It does assume that the
+ * final function doesn't damage the running transition value. (Some
+ * C-coded aggregates do that for efficiency's sake --- but they are
+ * supposed to do so only when their fcinfo->context is an AggState, not
+ * a WindowAggState.)
*
- * When there's peer rows, all rows in a peer group will have the same
- * aggregate values. The values will be calculated when current position
- * reaches the first peer row, and on all the following peer rows we will
- * just return the saved results.
+ * In many common cases, multiple rows share the same frame and hence
+ * the same aggregate value. (In particular, if there's no ORDER BY in
+ * a RANGE window, then all rows are peers and so they all have window
+ * frame equal to the whole partition.) We optimize such cases by
+ * calculating the aggregate value once when we reach the first row of a
+ * peer group, and then returning the saved value for all subsequent rows.
*
- * 'aggregatedupto' keeps track of the last row that has already been
- * accumulated for the aggregates. When the current row has no peers,
- * aggregatedupto will be the same as the current row after this
- * function. If there are peer rows, all peers will be accumulated in one
- * call of this function, and aggregatedupto will be ahead of the current
- * position. If there's no ORDER BY, and thus all rows are peers, the
- * first call will aggregate all rows in the partition.
+ * 'aggregatedupto' keeps track of the first row that has not yet been
+ * accumulated into the aggregate transition values. Whenever we start a
+ * new peer group, we accumulate forward to the end of the peer group.
*
- * TODO: In the future, we could implement sliding frames by recalculating
- * the aggregate whenever a row exits the frame. That would be pretty
- * slow, though. For aggregates like SUM and COUNT we could implement a
- * "negative transition function" that would be called for all the rows
- * that exit the frame.
+ * TODO: In the future, we should implement the full SQL-standard set
+ * of framing rules. We could implement the other cases by recalculating
+ * the aggregates whenever a row exits the frame. That would be pretty
+ * slow, though. For aggregates like SUM and COUNT we could implement a
+ * "negative transition function" that would be called for each row as it
+ * exits the frame. We'd have to think about avoiding recalculation of
+ * volatile arguments of aggregate functions, too.
*/
/*
* If we've already aggregated up through current row, reuse the
- * saved result values
+ * saved result values. NOTE: this test works for the currently
+ * supported framing rules, but will need fixing when more are added.
*/
if (winstate->aggregatedupto > winstate->currentpos)
{
}
/* Initialize aggregates on first call for partition */
- for (i = 0; i < numaggs; i++)
+ if (winstate->currentpos == 0)
{
- peraggstate = &winstate->peragg[i];
- wfuncno = peraggstate->wfuncno;
- if (!peraggstate->hasResult)
+ for (i = 0; i < numaggs; i++)
+ {
+ peraggstate = &winstate->peragg[i];
+ wfuncno = peraggstate->wfuncno;
initialize_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
- &winstate->peragg[i]);
- }
-
- /*
- * If this is the first call for this partition, fetch the first row
- * for comparing peer rows. On subsequent calls, we'll always read
- * ahead until we reach the first non-peer row, and store that row in
- * first_peer_slot, for use in the next call.
- */
- if (TupIsNull(first_peer_slot))
- {
- spool_tuples(winstate, winstate->aggregatedupto);
- tuplestore_select_read_pointer(winstate->buffer, winstate->agg_ptr);
- if (!tuplestore_gettupleslot(winstate->buffer, true, first_peer_slot))
- elog(ERROR, "unexpected end of tuplestore");
+ peraggstate);
+ }
}
/*
- * Advance until we reach the next non-peer row
+ * Advance until we reach a row not in frame (or end of partition).
+ *
+ * Note the loop invariant: agg_row_slot is either empty or holds the
+ * row at position aggregatedupto. The agg_ptr read pointer must always
+ * point to the next row to read into agg_row_slot.
*/
- first = true;
+ agg_row_slot = winstate->agg_row_slot;
for (;;)
{
- if (!first)
+ /* Fetch next row if we didn't already */
+ if (TupIsNull(agg_row_slot))
{
- /* Fetch the next row, and see if it's a peer */
spool_tuples(winstate, winstate->aggregatedupto);
tuplestore_select_read_pointer(winstate->buffer,
winstate->agg_ptr);
- slot = winstate->temp_slot_1;
- if (!tuplestore_gettupleslot(winstate->buffer, true, slot))
- break;
- if (!are_peers(winstate, first_peer_slot, slot))
- {
- ExecCopySlot(first_peer_slot, slot);
- break;
- }
- }
- else
- {
- /*
- * On first iteration, just accumulate the tuple saved from
- * last call
- */
- slot = first_peer_slot;
- first = false;
+ if (!tuplestore_gettupleslot(winstate->buffer, true, agg_row_slot))
+ break; /* must be end of partition */
}
- /* set tuple context for evaluation of aggregate arguments */
- winstate->tmpcontext->ecxt_outertuple = slot;
+ /* Exit loop (for now) if not in frame */
+ if (!row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot))
+ break;
+ /* Set tuple context for evaluation of aggregate arguments */
+ winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
+
+ /* Accumulate row into the aggregates */
for (i = 0; i < numaggs; i++)
{
- wfuncno = winstate->peragg[i].wfuncno;
-
+ peraggstate = &winstate->peragg[i];
+ wfuncno = peraggstate->wfuncno;
advance_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
- &winstate->peragg[i]);
-
+ peraggstate);
}
+
/* Reset per-input-tuple context after each tuple */
ResetExprContext(winstate->tmpcontext);
+
+ /* And advance the aggregated-row state */
winstate->aggregatedupto++;
+ ExecClearTuple(agg_row_slot);
}
/*
isnull = &econtext->ecxt_aggnulls[wfuncno];
finalize_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
- peraggstate, result, isnull);
+ peraggstate,
+ result, isnull);
/*
- * save the result for the next (non-shrinking frame) call.
+ * save the result in case next row shares the same frame.
+ *
+ * XXX in some framing modes, eg ROWS/END_CURRENT_ROW, we can know
+ * in advance that the next row can't possibly share the same frame.
+ * Is it worth detecting that and skipping this code?
*/
- if (!peraggstate->resulttypeByVal && !*isnull)
+ if (!peraggstate->resulttypeByVal)
{
/*
- * clear old resultValue in order not to leak memory.
+ * clear old resultValue in order not to leak memory. (Note:
+ * the new result can't possibly be the same datum as old
+ * resultValue, because we never passed it to the trans function.)
*/
- if (peraggstate->hasResult &&
- (DatumGetPointer(peraggstate->resultValue) !=
- DatumGetPointer(*result)) &&
- !peraggstate->resultValueIsNull)
+ if (!peraggstate->resultValueIsNull)
pfree(DatumGetPointer(peraggstate->resultValue));
/*
* If pass-by-ref, copy it into our global context.
*/
- oldContext = MemoryContextSwitchTo(winstate->wincontext);
- peraggstate->resultValue = datumCopy(*result,
- peraggstate->resulttypeByVal,
- peraggstate->resulttypeLen);
- MemoryContextSwitchTo(oldContext);
+ if (!*isnull)
+ {
+ oldContext = MemoryContextSwitchTo(winstate->wincontext);
+ peraggstate->resultValue =
+ datumCopy(*result,
+ peraggstate->resulttypeByVal,
+ peraggstate->resulttypeLen);
+ MemoryContextSwitchTo(oldContext);
+ }
}
else
{
peraggstate->resultValue = *result;
}
peraggstate->resultValueIsNull = *isnull;
- peraggstate->hasResult = true;
}
}
int i;
winstate->partition_spooled = false;
+ winstate->frametail_valid = false;
winstate->spooled_rows = 0;
winstate->currentpos = 0;
winstate->frametailpos = -1;
winstate->aggregatedupto = 0;
+ ExecClearTuple(winstate->agg_row_slot);
/*
* If this is the very first partition, we need to fetch the first
- * input row to store in it.
+ * input row to store in first_part_slot.
*/
if (TupIsNull(winstate->first_part_slot))
{
/* Release any partition-local state of this window function */
if (perfuncstate->winobj)
perfuncstate->winobj->localmem = NULL;
-
- /* Reset agg result cache */
- if (perfuncstate->plain_agg)
- {
- int aggno = perfuncstate->aggno;
- WindowStatePerAggData *peraggstate = &winstate->peragg[aggno];
-
- peraggstate->resultValueIsNull = true;
- peraggstate->hasResult = false;
- }
}
/*
* Release all partition-local memory (in particular, any partition-local
- * state or aggregate temp data that we might have trashed our pointers
- * to in the above loop). We don't rely on retail pfree because some
+ * state that we might have trashed our pointers to in the above loop, and
+ * any aggregate temp data). We don't rely on retail pfree because some
* aggregates might have allocated data we don't have direct pointers to.
*/
MemoryContextResetAndDeleteChildren(winstate->wincontext);
- /* Ensure eval_windowaggregates will see next call as partition start */
- ExecClearTuple(winstate->first_peer_slot);
-
if (winstate->buffer)
tuplestore_end(winstate->buffer);
winstate->buffer = NULL;
winstate->partition_spooled = false;
}
+/*
+ * row_is_in_frame
+ * Determine whether a row is in the current row's window frame according
+ * to our window framing rule
+ *
+ * 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.
+ */
+static bool
+row_is_in_frame(WindowAggState *winstate, int64 pos, TupleTableSlot *slot)
+{
+ WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
+ int frameOptions = node->frameOptions;
+
+ Assert(pos >= 0); /* else caller error */
+
+ /* We only support frame start mode UNBOUNDED PRECEDING for now */
+ Assert(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING);
+
+ /* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
+ if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
+ return true;
+
+ /* Else frame tail mode must be CURRENT ROW */
+ Assert(frameOptions & FRAMEOPTION_END_CURRENT_ROW);
+
+ /* if row is current row or a predecessor, it must be in frame */
+ if (pos <= winstate->currentpos)
+ return true;
+
+ /* In ROWS mode, *only* such rows are in frame */
+ if (frameOptions & FRAMEOPTION_ROWS)
+ return false;
+
+ /* Else must be RANGE mode */
+ Assert(frameOptions & FRAMEOPTION_RANGE);
+
+ /* In frame iff it's a peer of current row */
+ return are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot);
+}
+
+/*
+ * update_frametailpos
+ * make frametailpos valid for the current row
+ *
+ * Uses the winobj's read pointer for any required fetches; the winobj's
+ * mark must not be past the currently known frame tail. Also uses the
+ * specified slot for any required fetches.
+ */
+static void
+update_frametailpos(WindowObject winobj, TupleTableSlot *slot)
+{
+ WindowAggState *winstate = winobj->winstate;
+ WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan;
+ int frameOptions = node->frameOptions;
+ int64 ftnext;
+
+ if (winstate->frametail_valid)
+ return; /* already known for current row */
+
+ /* We only support frame start mode UNBOUNDED PRECEDING for now */
+ Assert(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING);
+
+ /* In UNBOUNDED FOLLOWING mode, all partition rows are in frame */
+ if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
+ {
+ spool_tuples(winstate, -1);
+ winstate->frametailpos = winstate->spooled_rows - 1;
+ winstate->frametail_valid = true;
+ return;
+ }
+
+ /* Else frame tail mode must be CURRENT ROW */
+ Assert(frameOptions & FRAMEOPTION_END_CURRENT_ROW);
+
+ /* In ROWS mode, exactly the rows up to current are in frame */
+ if (frameOptions & FRAMEOPTION_ROWS)
+ {
+ winstate->frametailpos = winstate->currentpos;
+ winstate->frametail_valid = true;
+ return;
+ }
+
+ /* Else must be RANGE mode */
+ Assert(frameOptions & FRAMEOPTION_RANGE);
+
+ /* If no ORDER BY, all rows are peers with each other */
+ if (node->ordNumCols == 0)
+ {
+ spool_tuples(winstate, -1);
+ winstate->frametailpos = winstate->spooled_rows - 1;
+ winstate->frametail_valid = true;
+ return;
+ }
+
+ /*
+ * Else we have to search for the first non-peer of the current row.
+ * We assume the current value of frametailpos is a lower bound on the
+ * possible frame tail location, ie, frame tail never goes backward, and
+ * that currentpos is also a lower bound, ie, current row is always in
+ * frame.
+ */
+ ftnext = Max(winstate->frametailpos, winstate->currentpos) + 1;
+ for (;;)
+ {
+ if (!window_gettupleslot(winobj, ftnext, slot))
+ break; /* end of partition */
+ if (!are_peers(winstate, slot, winstate->ss.ss_ScanTupleSlot))
+ break; /* not peer of current row */
+ ftnext++;
+ }
+ winstate->frametailpos = ftnext - 1;
+ winstate->frametail_valid = true;
+}
+
/* -----------------
* ExecWindowAgg
{
/* Initialize for first partition and set current row = 0 */
begin_partition(winstate);
+ /* If there are no input rows, we'll detect that and exit below */
}
else
{
/* Advance current row within partition */
winstate->currentpos++;
+ /* This might mean that the frame tail moves, too */
+ winstate->frametail_valid = false;
}
/*
ResetExprContext(econtext);
/*
- * Read the current row from the tuplestore, and save in ScanTupleSlot
- * for possible use by WinGetFuncArgCurrent or the final projection step.
+ * Read the current row from the tuplestore, and save in ScanTupleSlot.
* (We can't rely on the outerplan's output slot because we may have to
* read beyond the current row.)
*
ExecInitScanTupleSlot(estate, &winstate->ss);
ExecInitResultTupleSlot(estate, &winstate->ss.ps);
winstate->first_part_slot = ExecInitExtraTupleSlot(estate);
- winstate->first_peer_slot = ExecInitExtraTupleSlot(estate);
+ winstate->agg_row_slot = ExecInitExtraTupleSlot(estate);
winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate);
winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate);
ExecSetSlotDescriptor(winstate->first_part_slot,
winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
- ExecSetSlotDescriptor(winstate->first_peer_slot,
+ ExecSetSlotDescriptor(winstate->agg_row_slot,
winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
ExecSetSlotDescriptor(winstate->temp_slot_1,
winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
AclResult aclresult;
int i;
+ if (wfunc->winref != node->winref) /* planner screwed up? */
+ elog(ERROR, "WindowFunc with winref %u assigned to WindowAgg with winref %u",
+ wfunc->winref, node->winref);
+
/* Look for a previous duplicate window function */
for (i = 0; i <= wfuncno; i++)
{
ExecClearTuple(node->ss.ss_ScanTupleSlot);
ExecClearTuple(node->first_part_slot);
- ExecClearTuple(node->first_peer_slot);
+ ExecClearTuple(node->agg_row_slot);
ExecClearTuple(node->temp_slot_1);
ExecClearTuple(node->temp_slot_2);
/* release all temp tuples, but especially first_part_slot */
ExecClearTuple(node->ss.ss_ScanTupleSlot);
ExecClearTuple(node->first_part_slot);
- ExecClearTuple(node->first_peer_slot);
+ ExecClearTuple(node->agg_row_slot);
ExecClearTuple(node->temp_slot_1);
ExecClearTuple(node->temp_slot_2);
/*
* are_peers
* compare two rows to see if they are equal according to the ORDER BY clause
+ *
+ * NB: this does not consider the window frame mode.
*/
static bool
are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
/*
* window_gettupleslot
- * Fetch the pos'th tuple of the current partition into the slot
+ * Fetch the pos'th tuple of the current partition into the slot,
+ * using the winobj's read pointer
*
* Returns true if successful, false if no such row
*/
* WinRowsArePeers
* Compare two rows (specified by absolute position in window) to see
* if they are equal according to the ORDER BY clause.
+ *
+ * NB: this does not consider the window frame mode.
*/
bool
WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2)
bool res;
Assert(WindowObjectIsValid(winobj));
-
winstate = winobj->winstate;
node = (WindowAgg *) winstate->ss.ps.plan;
int relpos, int seektype, bool set_mark,
bool *isnull, bool *isout)
{
+ WindowAggState *winstate;
ExprContext *econtext;
TupleTableSlot *slot;
bool gottuple;
int64 abs_pos;
Assert(WindowObjectIsValid(winobj));
-
- econtext = winobj->winstate->ss.ps.ps_ExprContext;
- slot = winobj->winstate->temp_slot_1;
+ winstate = winobj->winstate;
+ econtext = winstate->ss.ps.ps_ExprContext;
+ slot = winstate->temp_slot_1;
switch (seektype)
{
case WINDOW_SEEK_CURRENT:
- abs_pos = winobj->winstate->currentpos + relpos;
+ abs_pos = winstate->currentpos + relpos;
break;
case WINDOW_SEEK_HEAD:
abs_pos = relpos;
break;
case WINDOW_SEEK_TAIL:
- spool_tuples(winobj->winstate, -1);
- abs_pos = winobj->winstate->spooled_rows - 1 + relpos;
+ spool_tuples(winstate, -1);
+ abs_pos = winstate->spooled_rows - 1 + relpos;
break;
default:
elog(ERROR, "unrecognized window seek type: %d", seektype);
break;
}
- if (abs_pos >= 0)
- gottuple = window_gettupleslot(winobj, abs_pos, slot);
- else
- gottuple = false;
+ gottuple = window_gettupleslot(winobj, abs_pos, slot);
if (!gottuple)
{
int relpos, int seektype, bool set_mark,
bool *isnull, bool *isout)
{
+ WindowAggState *winstate;
ExprContext *econtext;
TupleTableSlot *slot;
bool gottuple;
int64 abs_pos;
- int64 frametailpos;
Assert(WindowObjectIsValid(winobj));
-
- /* if no ordering columns, partition and frame are the same thing */
- if (((WindowAgg *) winobj->winstate->ss.ps.plan)->ordNumCols == 0)
- return WinGetFuncArgInPartition(winobj, argno, relpos, seektype,
- set_mark, isnull, isout);
-
- econtext = winobj->winstate->ss.ps.ps_ExprContext;
- slot = winobj->winstate->temp_slot_1;
- frametailpos = winobj->winstate->frametailpos;
+ winstate = winobj->winstate;
+ econtext = winstate->ss.ps.ps_ExprContext;
+ slot = winstate->temp_slot_1;
switch (seektype)
{
case WINDOW_SEEK_CURRENT:
- abs_pos = winobj->winstate->currentpos + relpos;
+ abs_pos = winstate->currentpos + relpos;
break;
case WINDOW_SEEK_HEAD:
abs_pos = relpos;
break;
case WINDOW_SEEK_TAIL:
- /* abs_pos is calculated later */
- abs_pos = 0; /* keep compiler quiet */
+ update_frametailpos(winobj, slot);
+ abs_pos = winstate->frametailpos + relpos;
break;
default:
elog(ERROR, "unrecognized window seek type: %d", seektype);
break;
}
- /*
- * Seek for frame tail. If the tail position is before current,
- * always check if the tail is after the current or not.
- */
- if (frametailpos <= winobj->winstate->currentpos)
- {
- int64 add = 1;
-
- for (;;)
- {
- spool_tuples(winobj->winstate, winobj->winstate->currentpos + add);
- if (winobj->winstate->spooled_rows > winobj->winstate->currentpos + add)
- {
- /*
- * When seektype is not TAIL, we may optimize not to
- * spool unnecessary tuples. In TAIL mode, we need to search
- * until we find a row that's definitely not a peer.
- */
- if (!WinRowsArePeers(winobj, winobj->winstate->currentpos,
- winobj->winstate->currentpos + add) ||
- (seektype != WINDOW_SEEK_TAIL &&
- winobj->winstate->currentpos + add < abs_pos))
- break;
- add++;
- }
- else
- {
- /*
- * If hit the partition end, the last row is the frame tail.
- */
- break;
- }
- }
- frametailpos = winobj->winstate->currentpos + add - 1;
- winobj->winstate->frametailpos = frametailpos;
- }
-
- if (seektype == WINDOW_SEEK_TAIL)
- {
- abs_pos = frametailpos + relpos;
- }
-
- /*
- * If there is an ORDER BY (we don't support other window frame
- * specifications yet), the frame runs from first row of the partition
- * to the last peer of the current row. Otherwise the frame is the
- * whole partition.
- */
- if (abs_pos < 0 || abs_pos > frametailpos)
- gottuple = false;
- else
- gottuple = window_gettupleslot(winobj, abs_pos, slot);
+ gottuple = window_gettupleslot(winobj, abs_pos, slot);
+ if (gottuple)
+ gottuple = row_is_in_frame(winstate, abs_pos, slot);
if (!gottuple)
{
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.417 2008/12/28 18:53:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.418 2008/12/31 00:08:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
CopyPlanFields((Plan *) from, (Plan *) newnode);
+ COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(partNumCols);
if (from->partNumCols > 0)
{
COPY_POINTER_FIELD(ordColIdx, from->ordNumCols * sizeof(AttrNumber));
COPY_POINTER_FIELD(ordOperators, from->ordNumCols * sizeof(Oid));
}
+ COPY_SCALAR_FIELD(frameOptions);
return newnode;
}
COPY_STRING_FIELD(refname);
COPY_NODE_FIELD(partitionClause);
COPY_NODE_FIELD(orderClause);
+ COPY_SCALAR_FIELD(frameOptions);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(copiedOrder);
COPY_STRING_FIELD(refname);
COPY_NODE_FIELD(partitionClause);
COPY_NODE_FIELD(orderClause);
+ COPY_SCALAR_FIELD(frameOptions);
COPY_LOCATION_FIELD(location);
return newnode;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.342 2008/12/28 18:53:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.343 2008/12/31 00:08:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
COMPARE_STRING_FIELD(refname);
COMPARE_NODE_FIELD(partitionClause);
COMPARE_NODE_FIELD(orderClause);
+ COMPARE_SCALAR_FIELD(frameOptions);
COMPARE_LOCATION_FIELD(location);
return true;
COMPARE_STRING_FIELD(refname);
COMPARE_NODE_FIELD(partitionClause);
COMPARE_NODE_FIELD(orderClause);
+ COMPARE_SCALAR_FIELD(frameOptions);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(copiedOrder);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.347 2008/12/28 18:53:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.348 2008/12/31 00:08:36 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
_outPlanInfo(str, (Plan *) node);
+ WRITE_UINT_FIELD(winref);
WRITE_INT_FIELD(partNumCols);
appendStringInfo(str, " :partColIdx");
appendStringInfo(str, " :ordOperations");
for (i = 0; i < node->ordNumCols; i++)
appendStringInfo(str, " %u", node->ordOperators[i]);
+
+ WRITE_INT_FIELD(frameOptions);
}
static void
WRITE_STRING_FIELD(refname);
WRITE_NODE_FIELD(partitionClause);
WRITE_NODE_FIELD(orderClause);
+ WRITE_INT_FIELD(frameOptions);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(copiedOrder);
}
WRITE_STRING_FIELD(refname);
WRITE_NODE_FIELD(partitionClause);
WRITE_NODE_FIELD(orderClause);
+ WRITE_INT_FIELD(frameOptions);
WRITE_LOCATION_FIELD(location);
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.218 2008/12/28 18:53:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.219 2008/12/31 00:08:36 tgl Exp $
*
* NOTES
* Path and Plan nodes do not have any readfuncs support, because we
READ_STRING_FIELD(refname);
READ_NODE_FIELD(partitionClause);
READ_NODE_FIELD(orderClause);
+ READ_INT_FIELD(frameOptions);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(copiedOrder);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.253 2008/12/28 18:53:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.254 2008/12/31 00:08:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
}
WindowAgg *
-make_windowagg(PlannerInfo *root, List *tlist, int numWindowFuncs,
+make_windowagg(PlannerInfo *root, List *tlist,
+ int numWindowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
- Plan *lefttree)
+ int frameOptions, Plan *lefttree)
{
WindowAgg *node = makeNode(WindowAgg);
Plan *plan = &node->plan;
Path windowagg_path; /* dummy for result of cost_windowagg */
QualCost qual_cost;
+ node->winref = winref;
node->partNumCols = partNumCols;
node->partColIdx = partColIdx;
node->partOperators = partOperators;
node->ordNumCols = ordNumCols;
node->ordColIdx = ordColIdx;
node->ordOperators = ordOperators;
+ node->frameOptions = frameOptions;
copy_plan_costsize(plan, lefttree); /* only care about copying size */
cost_windowagg(&windowagg_path, root,
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.248 2008/12/28 18:53:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.249 2008/12/31 00:08:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
make_windowagg(root,
(List *) copyObject(window_tlist),
list_length(wflists->windowFuncs[wc->winref]),
+ wc->winref,
partNumCols,
partColIdx,
partOperators,
ordNumCols,
ordColIdx,
ordOperators,
+ wc->frameOptions,
result_plan);
}
}
WindowClause *wc2 = (WindowClause *) lfirst(lc);
next = lnext(lc);
+ /* framing options are NOT to be compared here! */
if (equal(wc->partitionClause, wc2->partitionClause) &&
equal(wc->orderClause, wc2->orderClause))
{
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.648 2008/12/28 18:53:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.649 2008/12/31 00:08:36 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
%type <list> window_clause window_definition_list opt_partition_clause
%type <windef> window_definition over_clause window_specification
%type <str> opt_existing_window_name
+%type <ival> opt_frame_clause frame_extent frame_bound
/*
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD
+ FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD
FREEZE FROM FULL FUNCTION
GLOBAL GRANT GRANTED GREATEST GROUP_P
ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
PARSER PARTIAL PARTITION PASSWORD PLACING PLANS POSITION
- PRECISION PRESERVE PREPARE PREPARED PRIMARY
+ PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE
QUOTE
- READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX RELATIVE_P RELEASE
- RENAME REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURNING RETURNS
- REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
+ RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
+ RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
+ RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P
- UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL
+ UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL
UPDATE USER USING
VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
* between POSTFIXOP and Op. We can safely assign the same priority to
* 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
- * to support opt_existing_window_name.
+ * 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.
*/
-%nonassoc IDENT PARTITION
+%nonassoc IDENT PARTITION RANGE ROWS
%left Op OPERATOR /* multi-character ops and user-defined operators */
%nonassoc NOTNULL
%nonassoc ISNULL
| OVER ColId
{
WindowDef *n = makeNode(WindowDef);
- n->name = NULL;
- n->refname = $2;
+ n->name = $2;
+ n->refname = NULL;
n->partitionClause = NIL;
n->orderClause = NIL;
+ n->frameOptions = FRAMEOPTION_DEFAULTS;
n->location = @2;
$$ = n;
}
;
window_specification: '(' opt_existing_window_name opt_partition_clause
- opt_sort_clause ')'
+ opt_sort_clause opt_frame_clause ')'
{
WindowDef *n = makeNode(WindowDef);
n->name = NULL;
n->refname = $2;
n->partitionClause = $3;
n->orderClause = $4;
+ n->frameOptions = $5;
n->location = @1;
$$ = n;
}
* that the shift/reduce conflict is resolved in favor of reducing the rule.
* These keywords are thus precluded from being an existing_window_name but
* are not reserved for any other purpose.
- * (RANGE/ROWS are not an issue as of 8.4 for lack of frame_clause support.)
*/
opt_existing_window_name: ColId { $$ = $1; }
| /*EMPTY*/ %prec Op { $$ = NULL; }
| /*EMPTY*/ { $$ = NIL; }
;
+/*
+ * This is only a subset of the full SQL:2008 frame_clause grammar.
+ * We don't support <expression> PRECEDING, <expression> FOLLOWING,
+ * nor <window frame exclusion> yet.
+ */
+opt_frame_clause:
+ RANGE frame_extent
+ {
+ $$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_RANGE | $2;
+ }
+ | ROWS frame_extent
+ {
+ $$ = FRAMEOPTION_NONDEFAULT | FRAMEOPTION_ROWS | $2;
+ }
+ | /*EMPTY*/
+ { $$ = FRAMEOPTION_DEFAULTS; }
+ ;
+
+frame_extent: frame_bound
+ {
+ /* reject invalid cases */
+ if ($1 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
+ scanner_errposition(@1)));
+ if ($1 & FRAMEOPTION_START_CURRENT_ROW)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("frame start at CURRENT ROW is not implemented"),
+ scanner_errposition(@1)));
+ $$ = $1 | FRAMEOPTION_END_CURRENT_ROW;
+ }
+ | BETWEEN frame_bound AND frame_bound
+ {
+ /* reject invalid cases */
+ if ($2 & FRAMEOPTION_START_UNBOUNDED_FOLLOWING)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("frame start cannot be UNBOUNDED FOLLOWING"),
+ scanner_errposition(@2)));
+ if ($2 & FRAMEOPTION_START_CURRENT_ROW)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("frame start at CURRENT ROW is not implemented"),
+ scanner_errposition(@2)));
+ if ($4 & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("frame end cannot be UNBOUNDED PRECEDING"),
+ scanner_errposition(@4)));
+ /* shift converts START_ options to END_ options */
+ $$ = FRAMEOPTION_BETWEEN | $2 | ($4 << 1);
+ }
+ ;
+
+/*
+ * This is used for both frame start and frame end, with output set up on
+ * the assumption it's frame start; the frame_extent productions must reject
+ * invalid cases.
+ */
+frame_bound:
+ UNBOUNDED PRECEDING
+ {
+ $$ = FRAMEOPTION_START_UNBOUNDED_PRECEDING;
+ }
+ | UNBOUNDED FOLLOWING
+ {
+ $$ = FRAMEOPTION_START_UNBOUNDED_FOLLOWING;
+ }
+ | CURRENT_P ROW
+ {
+ $$ = FRAMEOPTION_START_CURRENT_ROW;
+ }
+ ;
+
+
/*
* Supporting nonterminals for expressions.
*/
| EXTERNAL
| FAMILY
| FIRST_P
+ | FOLLOWING
| FORCE
| FORWARD
| FUNCTION
| PARTITION
| PASSWORD
| PLANS
+ | PRECEDING
| PREPARE
| PREPARED
| PRESERVE
| PROCEDURAL
| PROCEDURE
| QUOTE
+ | RANGE
| READ
| REASSIGN
| RECHECK
| TRUNCATE
| TRUSTED
| TYPE_P
+ | UNBOUNDED
| UNCOMMITTED
| UNENCRYPTED
| UNKNOWN
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.207 2008/12/28 18:53:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.208 2008/12/31 00:08:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{"fetch", FETCH, RESERVED_KEYWORD},
{"first", FIRST_P, UNRESERVED_KEYWORD},
{"float", FLOAT_P, COL_NAME_KEYWORD},
+ {"following", FOLLOWING, UNRESERVED_KEYWORD},
{"for", FOR, RESERVED_KEYWORD},
{"force", FORCE, UNRESERVED_KEYWORD},
{"foreign", FOREIGN, RESERVED_KEYWORD},
{"placing", PLACING, RESERVED_KEYWORD},
{"plans", PLANS, UNRESERVED_KEYWORD},
{"position", POSITION, COL_NAME_KEYWORD},
+ {"preceding", PRECEDING, UNRESERVED_KEYWORD},
{"precision", PRECISION, COL_NAME_KEYWORD},
{"prepare", PREPARE, UNRESERVED_KEYWORD},
{"prepared", PREPARED, UNRESERVED_KEYWORD},
{"procedural", PROCEDURAL, UNRESERVED_KEYWORD},
{"procedure", PROCEDURE, UNRESERVED_KEYWORD},
{"quote", QUOTE, UNRESERVED_KEYWORD},
+ {"range", RANGE, UNRESERVED_KEYWORD},
{"read", READ, UNRESERVED_KEYWORD},
{"real", REAL, COL_NAME_KEYWORD},
{"reassign", REASSIGN, UNRESERVED_KEYWORD},
{"truncate", TRUNCATE, UNRESERVED_KEYWORD},
{"trusted", TRUSTED, UNRESERVED_KEYWORD},
{"type", TYPE_P, UNRESERVED_KEYWORD},
+ {"unbounded", UNBOUNDED, UNRESERVED_KEYWORD},
{"uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD},
{"unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD},
{"union", UNION, RESERVED_KEYWORD},
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.85 2008/12/28 18:53:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.86 2008/12/31 00:08:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
locate_windowfunc((Node *) wfunc->args))));
/*
- * If the OVER clause just specifies a reference name, find that
+ * If the OVER clause just specifies a window name, find that
* WINDOW clause (which had better be present). Otherwise, try to
* match all the properties of the OVER clause, and make a new entry
* in the p_windowdefs list if no luck.
*/
- Assert(!windef->name);
- if (windef->refname &&
- windef->partitionClause == NIL &&
- windef->orderClause == NIL)
+ if (windef->name)
{
Index winref = 0;
ListCell *lc;
+ Assert(windef->refname == NULL &&
+ windef->partitionClause == NIL &&
+ windef->orderClause == NIL &&
+ windef->frameOptions == FRAMEOPTION_DEFAULTS);
+
foreach(lc, pstate->p_windowdefs)
{
WindowDef *refwin = (WindowDef *) lfirst(lc);
winref++;
- if (refwin->name && strcmp(refwin->name, windef->refname) == 0)
+ if (refwin->name && strcmp(refwin->name, windef->name) == 0)
{
wfunc->winref = winref;
break;
if (lc == NULL) /* didn't find it? */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("window \"%s\" does not exist", windef->refname),
+ errmsg("window \"%s\" does not exist", windef->name),
parser_errposition(pstate, windef->location)));
}
else
winref++;
if (refwin->refname && windef->refname &&
- strcmp(refwin->name, windef->refname) == 0)
+ strcmp(refwin->refname, windef->refname) == 0)
/* matched on refname */ ;
else if (!refwin->refname && !windef->refname)
/* matched, no refname */ ;
else
continue;
if (equal(refwin->partitionClause, windef->partitionClause) &&
- equal(refwin->orderClause, windef->orderClause))
+ equal(refwin->orderClause, windef->orderClause) &&
+ refwin->frameOptions == windef->frameOptions)
{
/* found a duplicate window specification */
wfunc->winref = winref;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.182 2008/12/28 18:53:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.183 2008/12/31 00:08:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Per spec, a windowdef that references a previous one copies the
* previous partition clause (and mustn't specify its own). It can
* specify its own ordering clause. but only if the previous one
- * had none.
+ * had none. It always specifies its own framing clause.
*/
if (refwc)
{
wc->orderClause = orderClause;
wc->copiedOrder = false;
}
+ wc->frameOptions = windef->frameOptions;
wc->winref = winref;
result = lappend(result, wc);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.291 2008/12/28 18:53:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.292 2008/12/31 00:08:37 tgl Exp $
*
*-------------------------------------------------------------------------
*/
appendStringInfoString(buf, quote_identifier(wc->refname));
needspace = true;
}
- /* partitions are always inherited, so only print if no refname */
+ /* partition clauses are always inherited, so only print if no refname */
if (wc->partitionClause && !wc->refname)
{
if (needspace)
}
needspace = true;
}
+ /* print ordering clause only if not inherited */
if (wc->orderClause && !wc->copiedOrder)
{
if (needspace)
get_rule_orderby(wc->orderClause, targetList, false, context);
needspace = true;
}
+ /* framing clause is never inherited, so print unless it's default */
+ if (wc->frameOptions & FRAMEOPTION_NONDEFAULT)
+ {
+ if (needspace)
+ appendStringInfoChar(buf, ' ');
+ if (wc->frameOptions & FRAMEOPTION_RANGE)
+ appendStringInfoString(buf, "RANGE ");
+ else if (wc->frameOptions & FRAMEOPTION_ROWS)
+ appendStringInfoString(buf, "ROWS ");
+ else
+ Assert(false);
+ if (wc->frameOptions & FRAMEOPTION_BETWEEN)
+ appendStringInfoString(buf, "BETWEEN ");
+ if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
+ appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
+ else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
+ appendStringInfoString(buf, "CURRENT ROW ");
+ else
+ Assert(false);
+ if (wc->frameOptions & FRAMEOPTION_BETWEEN)
+ {
+ appendStringInfoString(buf, "AND ");
+ if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
+ appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
+ else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
+ appendStringInfoString(buf, "CURRENT ROW ");
+ else
+ Assert(false);
+ }
+ /* we will now have a trailing space; remove it */
+ buf->len--;
+ }
appendStringInfoChar(buf, ')');
}
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.514 2008/12/28 18:53:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.515 2008/12/31 00:08:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200812281
+#define CATALOG_VERSION_NO 200812301
#endif
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.197 2008/12/28 18:54:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.198 2008/12/31 00:08:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* have been spooled into tuplestore */
bool more_partitions; /* true if there's more partitions after
* this one */
+ bool frametail_valid; /* true if frametailpos is known up to date
+ * for current row */
TupleTableSlot *first_part_slot; /* first tuple of current or next
* partition */
/* temporary slots for tuples fetched back from tuplestore */
- TupleTableSlot *first_peer_slot;
+ TupleTableSlot *agg_row_slot;
TupleTableSlot *temp_slot_1;
TupleTableSlot *temp_slot_2;
} WindowAggState;
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.385 2008/12/28 18:54:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.386 2008/12/31 00:08:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* WindowDef - raw representation of WINDOW and OVER clauses
+ *
+ * For entries in a WINDOW list, "name" is the window name being defined.
+ * For OVER clauses, we use "name" for the "OVER window" syntax, or "refname"
+ * for the "OVER (window)" syntax, which is subtly different --- the latter
+ * implies overriding the window frame clause.
*/
typedef struct WindowDef
{
NodeTag type;
- char *name; /* window name (NULL in an OVER clause) */
+ char *name; /* window's own name */
char *refname; /* referenced window name, if any */
List *partitionClause; /* PARTITION BY expression list */
List *orderClause; /* ORDER BY (list of SortBy) */
+ int frameOptions; /* frame_clause options, see below */
int location; /* parse location, or -1 if none/unknown */
} WindowDef;
+/*
+ * frameOptions is an OR of these bits. The NONDEFAULT and BETWEEN bits are
+ * used so that ruleutils.c can tell which properties were specified and
+ * 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_DEFAULTS \
+ (FRAMEOPTION_RANGE | FRAMEOPTION_START_UNBOUNDED_PRECEDING | \
+ FRAMEOPTION_END_CURRENT_ROW)
+
/*
* RangeSubselect - subquery appearing in a FROM clause
*/
* 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;
- * the orderClause might or might not be copied. (We don't implement
- * framing clauses yet, but if we did, they are never copied, per spec.)
+ * the orderClause might or might not be copied (see copiedOrder); the framing
+ * options are never copied, per spec.
*/
typedef struct WindowClause
{
char *refname; /* referenced window name, if any */
List *partitionClause; /* PARTITION BY list */
List *orderClause; /* ORDER BY list */
+ int frameOptions; /* frame_clause options, see WindowDef */
Index winref; /* ID referenced by window functions */
bool copiedOrder; /* did we copy orderClause from refname? */
} WindowClause;
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.106 2008/12/28 18:54:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.107 2008/12/31 00:08:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
typedef struct WindowAgg
{
Plan plan;
+ Index winref; /* ID referenced by window functions */
int partNumCols; /* number of columns in partition clause */
AttrNumber *partColIdx; /* their indexes in the target list */
Oid *partOperators; /* equality operators for partition columns */
int ordNumCols; /* number of columns in ordering clause */
AttrNumber *ordColIdx; /* their indexes in the target list */
Oid *ordOperators; /* equality operators for ordering columns */
+ int frameOptions; /* frame_clause options, see WindowDef */
} WindowAgg;
/* ----------------
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.115 2008/12/28 18:54:01 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.116 2008/12/31 00:08:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
long numGroups, int numAggs,
Plan *lefttree);
extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
- int numWindowFuncs,
+ int numWindowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
- Plan *lefttree);
+ int frameOptions, Plan *lefttree);
extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
double numGroups,
11 | develop | 5200 | 500 | 200 | 500 | 200
(10 rows)
+-- test non-default frame specifications
+SELECT four, ten,
+ sum(ten) over (partition by four order by ten),
+ last_value(ten) over (partition by four order by ten)
+FROM (select distinct ten, four from tenk1) ss;
+ four | ten | sum | last_value
+------+-----+-----+------------
+ 0 | 0 | 0 | 0
+ 0 | 2 | 2 | 2
+ 0 | 4 | 6 | 4
+ 0 | 6 | 12 | 6
+ 0 | 8 | 20 | 8
+ 1 | 1 | 1 | 1
+ 1 | 3 | 4 | 3
+ 1 | 5 | 9 | 5
+ 1 | 7 | 16 | 7
+ 1 | 9 | 25 | 9
+ 2 | 0 | 0 | 0
+ 2 | 2 | 2 | 2
+ 2 | 4 | 6 | 4
+ 2 | 6 | 12 | 6
+ 2 | 8 | 20 | 8
+ 3 | 1 | 1 | 1
+ 3 | 3 | 4 | 3
+ 3 | 5 | 9 | 5
+ 3 | 7 | 16 | 7
+ 3 | 9 | 25 | 9
+(20 rows)
+
+SELECT four, ten,
+ sum(ten) over (partition by four order by ten range between unbounded preceding and current row),
+ last_value(ten) over (partition by four order by ten range between unbounded preceding and current row)
+FROM (select distinct ten, four from tenk1) ss;
+ four | ten | sum | last_value
+------+-----+-----+------------
+ 0 | 0 | 0 | 0
+ 0 | 2 | 2 | 2
+ 0 | 4 | 6 | 4
+ 0 | 6 | 12 | 6
+ 0 | 8 | 20 | 8
+ 1 | 1 | 1 | 1
+ 1 | 3 | 4 | 3
+ 1 | 5 | 9 | 5
+ 1 | 7 | 16 | 7
+ 1 | 9 | 25 | 9
+ 2 | 0 | 0 | 0
+ 2 | 2 | 2 | 2
+ 2 | 4 | 6 | 4
+ 2 | 6 | 12 | 6
+ 2 | 8 | 20 | 8
+ 3 | 1 | 1 | 1
+ 3 | 3 | 4 | 3
+ 3 | 5 | 9 | 5
+ 3 | 7 | 16 | 7
+ 3 | 9 | 25 | 9
+(20 rows)
+
+SELECT four, ten,
+ sum(ten) over (partition by four order by ten range between unbounded preceding and unbounded following),
+ last_value(ten) over (partition by four order by ten range between unbounded preceding and unbounded following)
+FROM (select distinct ten, four from tenk1) ss;
+ four | ten | sum | last_value
+------+-----+-----+------------
+ 0 | 0 | 20 | 8
+ 0 | 2 | 20 | 8
+ 0 | 4 | 20 | 8
+ 0 | 6 | 20 | 8
+ 0 | 8 | 20 | 8
+ 1 | 1 | 25 | 9
+ 1 | 3 | 25 | 9
+ 1 | 5 | 25 | 9
+ 1 | 7 | 25 | 9
+ 1 | 9 | 25 | 9
+ 2 | 0 | 20 | 8
+ 2 | 2 | 20 | 8
+ 2 | 4 | 20 | 8
+ 2 | 6 | 20 | 8
+ 2 | 8 | 20 | 8
+ 3 | 1 | 25 | 9
+ 3 | 3 | 25 | 9
+ 3 | 5 | 25 | 9
+ 3 | 7 | 25 | 9
+ 3 | 9 | 25 | 9
+(20 rows)
+
+SELECT four, ten/4 as two,
+ sum(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row),
+ last_value(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row)
+FROM (select distinct ten, four from tenk1) ss;
+ four | two | sum | last_value
+------+-----+-----+------------
+ 0 | 0 | 0 | 0
+ 0 | 0 | 0 | 0
+ 0 | 1 | 2 | 1
+ 0 | 1 | 2 | 1
+ 0 | 2 | 4 | 2
+ 1 | 0 | 0 | 0
+ 1 | 0 | 0 | 0
+ 1 | 1 | 2 | 1
+ 1 | 1 | 2 | 1
+ 1 | 2 | 4 | 2
+ 2 | 0 | 0 | 0
+ 2 | 0 | 0 | 0
+ 2 | 1 | 2 | 1
+ 2 | 1 | 2 | 1
+ 2 | 2 | 4 | 2
+ 3 | 0 | 0 | 0
+ 3 | 0 | 0 | 0
+ 3 | 1 | 2 | 1
+ 3 | 1 | 2 | 1
+ 3 | 2 | 4 | 2
+(20 rows)
+
+SELECT four, ten/4 as two,
+ sum(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row),
+ last_value(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row)
+FROM (select distinct ten, four from tenk1) ss;
+ four | two | sum | last_value
+------+-----+-----+------------
+ 0 | 0 | 0 | 0
+ 0 | 0 | 0 | 0
+ 0 | 1 | 1 | 1
+ 0 | 1 | 2 | 1
+ 0 | 2 | 4 | 2
+ 1 | 0 | 0 | 0
+ 1 | 0 | 0 | 0
+ 1 | 1 | 1 | 1
+ 1 | 1 | 2 | 1
+ 1 | 2 | 4 | 2
+ 2 | 0 | 0 | 0
+ 2 | 0 | 0 | 0
+ 2 | 1 | 1 | 1
+ 2 | 1 | 2 | 1
+ 2 | 2 | 4 | 2
+ 3 | 0 | 0 | 0
+ 3 | 0 | 0 | 0
+ 3 | 1 | 1 | 1
+ 3 | 1 | 2 | 1
+ 3 | 2 | 4 | 2
+(20 rows)
+
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
count
-------
(0 rows)
--- via a VIEW
-CREATE TEMPORARY VIEW vsumsalary AS
-SELECT SUM(salary) OVER (PARTITION BY depname) FROM empsalary;
-SELECT * FROM vsumsalary;
- sum
--------
- 25100
- 25100
- 25100
- 25100
- 25100
- 7400
- 7400
- 14600
- 14600
- 14600
-(10 rows)
-
-- ordering by a non-integer constant is allowed
SELECT rank() OVER (ORDER BY length('abc'));
rank
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
ERROR: argument of nth_value must be greater than zero
-- cleanup
-DROP VIEW vsumsalary;
DROP TABLE empsalary;
THEN 200 END AS depadj FROM empsalary
)s;
+-- test non-default frame specifications
+SELECT four, ten,
+ sum(ten) over (partition by four order by ten),
+ last_value(ten) over (partition by four order by ten)
+FROM (select distinct ten, four from tenk1) ss;
+
+SELECT four, ten,
+ sum(ten) over (partition by four order by ten range between unbounded preceding and current row),
+ last_value(ten) over (partition by four order by ten range between unbounded preceding and current row)
+FROM (select distinct ten, four from tenk1) ss;
+
+SELECT four, ten,
+ sum(ten) over (partition by four order by ten range between unbounded preceding and unbounded following),
+ last_value(ten) over (partition by four order by ten range between unbounded preceding and unbounded following)
+FROM (select distinct ten, four from tenk1) ss;
+
+SELECT four, ten/4 as two,
+ sum(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row),
+ last_value(ten/4) over (partition by four order by ten/4 range between unbounded preceding and current row)
+FROM (select distinct ten, four from tenk1) ss;
+
+SELECT four, ten/4 as two,
+ sum(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row),
+ last_value(ten/4) over (partition by four order by ten/4 rows between unbounded preceding and current row)
+FROM (select distinct ten, four from tenk1) ss;
+
-- with UNION
SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SELECT * FROM tenk2)s LIMIT 0;
--- via a VIEW
-CREATE TEMPORARY VIEW vsumsalary AS
-SELECT SUM(salary) OVER (PARTITION BY depname) FROM empsalary;
-SELECT * FROM vsumsalary;
-
-- ordering by a non-integer constant is allowed
SELECT rank() OVER (ORDER BY length('abc'));
SELECT nth_value(four, 0) OVER (ORDER BY ten), ten, four FROM tenk1;
-- cleanup
-DROP VIEW vsumsalary;
DROP TABLE empsalary;