Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions doc/sql.extensions/README.window_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Syntax:
ORDER BY <expr> [<direction>] [<nulls placement>] [, <expr> [<direction>] [<nulls placement>]] ...
<window frame> ::=
{RANGE | ROWS} <window frame extent> [<window frame exclusion>]
{RANGE | ROWS | GROUPS} <window frame extent> [<window frame exclusion>]
<window frame extent> ::=
{<window frame start> | <window frame between>}
Expand Down Expand Up @@ -272,17 +272,19 @@ And the result set:

It's possible to specify the frame that some window functions work. The frame is divided in three piecies: unit, start bound and end bound.

The unit `RANGE` or `ROWS` defines how the bounds `<expr> PRECEDING`, `<expr> FOLLOWING` and `CURRENT ROW` works.
The unit `RANGE`, `ROWS` or `GROUPS` defines how the bounds `<expr> PRECEDING`, `<expr> FOLLOWING` and `CURRENT ROW` works.

With `RANGE`, the `ORDER BY` should specify only one expression, and that expression should be of a numeric, date, time or timestamp type. For `<expr> PRECEDING` and `<expr> FOLLOWING` bounds, `<expr>` is respectively subtracted or added to the order expression, and for `CURRENT ROW` only the order expression is used. Then, all rows (inside the partition) between the bounds are considered part of the resulting window frame.

With `ROWS`, order expressions is not limited by number or types. In this case, `<expr> PRECEDING`, `<expr> FOLLOWING` and `CURRENT ROW` relates to the row position under the partition, and not to the order keys values.

`UNBOUNDED PRECEDING` and `UNBOUNDED FOLLOWING` work identically with `RANGE` and `ROWS`. `UNBOUNDED PRECEDING` looks for the first row and `UNBOUNDED FOLLOWING` the last one, always inside the partition.
With `GROUPS`, order expressions are not limited by number or types. In this case, `<expr> PRECEDING`, `<expr> FOLLOWING` and `CURRENT ROW` relate to peer groups under the partition. Peers are rows with the same `ORDER BY` values. If there is no `ORDER BY`, all rows in the partition are peers and the partition has one group.

`UNBOUNDED PRECEDING` and `UNBOUNDED FOLLOWING` work identically with `RANGE`, `ROWS` and `GROUPS`. `UNBOUNDED PRECEDING` looks for the first row and `UNBOUNDED FOLLOWING` the last one, always inside the partition.

The frame syntax with `<window frame start>` specifies the start frame, with the end frame being `CURRENT ROW`.

The optional frame exclusion clause (FB 6.0) is part of the frame clause and removes rows from the frame after its bounds have been evaluated. It can only be specified together with an explicit `ROWS` or `RANGE` frame. `EXCLUDE NO OTHERS` is the default and keeps the frame unchanged.
The optional frame exclusion clause (FB 6.0) is part of the frame clause and removes rows from the frame after its bounds have been evaluated. It can only be specified together with an explicit `ROWS`, `RANGE` or `GROUPS` frame. `EXCLUDE NO OTHERS` is the default and keeps the frame unchanged.

`EXCLUDE CURRENT ROW` removes only the current row from the frame.

Expand Down Expand Up @@ -377,7 +379,7 @@ Some window functions discard frames and frame exclusions. `ROW_NUMBER`, `LAG` a

## 6. Named windows (FB 4.0)

To avoid write repetitive or confusing expressions, windows can be named in a query with the `WINDOW` clause. A named window can be used in `OVER` to reference a window definition and can also be used as a base window of another named or inline (`OVER`) window. A window with frame (`ROWS` or `RANGE` clauses) can't be used as base window (but can be used with `OVER <window name>`). And a window with a base window can't have `PARTITION BY` nor can override `ORDER BY` of a base window.
To avoid write repetitive or confusing expressions, windows can be named in a query with the `WINDOW` clause. A named window can be used in `OVER` to reference a window definition and can also be used as a base window of another named or inline (`OVER`) window. A window with frame (`ROWS`, `RANGE` or `GROUPS` clauses) can't be used as base window (but can be used with `OVER <window name>`). And a window with a base window can't have `PARTITION BY` nor can override `ORDER BY` of a base window.

In a query with multiple `SELECT` and `WINDOW` clauses (for example, with subqueries), the window name scope is bound only to its query context, that is, a window name from an inner or outer context could not be used in another context. As such, the same window name definition could be used at different contexts.

Expand Down
1 change: 1 addition & 0 deletions src/common/ParserTokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ PARSER_TOKEN(TOK_GRANT, "GRANT", false)
PARSER_TOKEN(TOK_GRANTED, "GRANTED", true)
PARSER_TOKEN(TOK_GREATEST, "GREATEST", false)
PARSER_TOKEN(TOK_GROUP, "GROUP", false)
PARSER_TOKEN(TOK_GROUPS, "GROUPS", false)
PARSER_TOKEN(TOK_HASH, "HASH", true)
PARSER_TOKEN(TOK_HAVING, "HAVING", false)
PARSER_TOKEN(TOK_HEX_DECODE, "HEX_DECODE", true)
Expand Down
4 changes: 2 additions & 2 deletions src/dsql/ExprNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1492,8 +1492,8 @@ class WindowClause final : public DsqlNode<WindowClause, ExprNode::TYPE_WINDOW_C
{
// Warning: used in BLR
RANGE = 0,
ROWS
//// TODO: SQL-2013: GROUPS
ROWS,
GROUPS
};

public:
Expand Down
5 changes: 5 additions & 0 deletions src/dsql/parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ using namespace Firebird;
%token <metaNamePtr> FORMAT
%token <metaNamePtr> GENERATE_SERIES
%token <metaNamePtr> GREATEST
%token <metaNamePtr> GROUPS
%token <metaNamePtr> LEAST
%token <metaNamePtr> LISTAGG
%token <metaNamePtr> LTRIM
Expand Down Expand Up @@ -9003,6 +9004,10 @@ window_frame_extent
{ $$ = newNode<WindowClause::FrameExtent>(WindowClause::FrameExtent::Unit::RANGE); }
window_frame($2)
{ $$ = $2; }
| GROUPS
{ $$ = newNode<WindowClause::FrameExtent>(WindowClause::FrameExtent::Unit::GROUPS); }
window_frame($2)
{ $$ = $2; }
| ROWS
{ $$ = newNode<WindowClause::FrameExtent>(WindowClause::FrameExtent::Unit::ROWS); }
window_frame($2)
Expand Down
2 changes: 1 addition & 1 deletion src/include/firebird/impl/msg/jrd.h
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ FB_IMPL_MSG(JRD, 795, no_cursor, -901, "07", "005", "Cannot open cursor for non-
FB_IMPL_MSG(JRD, 796, dsql_window_incompat_frames, -104, "42", "000", "If <window frame bound 1> specifies @1, then <window frame bound 2> shall not specify @2")
FB_IMPL_MSG(JRD, 797, dsql_window_range_multi_key, -104, "42", "000", "RANGE based window with <expr> {PRECEDING | FOLLOWING} cannot have ORDER BY with more than one value")
FB_IMPL_MSG(JRD, 798, dsql_window_range_inv_key_type, -104, "42", "000", "RANGE based window with <offset> PRECEDING/FOLLOWING must have a single ORDER BY key of numerical, date, time or timestamp types")
FB_IMPL_MSG(JRD, 799, dsql_window_frame_value_inv_type, -104, "42", "000", "Window RANGE/ROWS PRECEDING/FOLLOWING value must be of a numerical type")
FB_IMPL_MSG(JRD, 799, dsql_window_frame_value_inv_type, -104, "42", "000", "Window RANGE/ROWS/GROUPS PRECEDING/FOLLOWING value must be of a numerical type")
FB_IMPL_MSG(JRD, 800, window_frame_value_invalid, -833, "42", "000", "Invalid PRECEDING or FOLLOWING offset in window function: cannot be negative")
FB_IMPL_MSG(JRD, 801, dsql_window_not_found, -833, "42", "000", "Window @1 not found")
FB_IMPL_MSG(JRD, 802, dsql_window_cant_overr_part, -833, "42", "000", "Cannot use PARTITION BY clause while overriding the window @1")
Expand Down
1 change: 1 addition & 0 deletions src/jrd/RecordSourceNodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2647,6 +2647,7 @@ void WindowSourceNode::parseWindow(thread_db* tdbb, CompilerScratch* csb)
{
case WindowClause::FrameExtent::Unit::RANGE:
case WindowClause::FrameExtent::Unit::ROWS:
case WindowClause::FrameExtent::Unit::GROUPS:
break;

default:
Expand Down
3 changes: 3 additions & 0 deletions src/jrd/recsrc/RecordSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,9 @@ namespace Jrd

SINT64 locateFrameRange(thread_db* tdbb, Request* request, Impure* impure,
const Frame* frame, const dsc* offsetDesc, SINT64 position) const;
SINT64 locateFrameGroups(thread_db* tdbb, Request* request, Impure* impure,
const Frame* frame, const impure_value_ex* offsetValue, SINT64 position,
bool startFrame) const;

private:
NestConst<SortNode> m_order;
Expand Down
114 changes: 107 additions & 7 deletions src/jrd/recsrc/WindowedStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ WindowedStream::WindowedStream(thread_db* tdbb, Optimizer* opt,
{
// While here, verify not supported functions/clauses.

if (window.order || window.frameExtent->unit == FrameExtent::Unit::ROWS)
if (window.order ||
window.frameExtent->unit == FrameExtent::Unit::ROWS ||
window.frameExtent->unit == FrameExtent::Unit::GROUPS)
{
for (const auto& source : window.map->sourceList)
{
Expand All @@ -241,7 +243,9 @@ WindowedStream::WindowedStream(thread_db* tdbb, Optimizer* opt,
if (arg)
{
string msg;
msg.printf("%s is not supported in windows with ORDER BY or frame by ROWS clauses", arg);
msg.printf(
"%s is not supported in windows with ORDER BY or frame by ROWS/GROUPS clauses",
arg);

status_exception::raise(
Arg::Gds(isc_wish_list) <<
Expand Down Expand Up @@ -660,7 +664,7 @@ bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const
if (m_frameExtent->frame1->value && !(m_invariantOffsets & 0x1))
getFrameValue(tdbb, request, m_frameExtent->frame1, &impure->startOffset);

// {range | rows} between unbounded preceding and ...
// {range | rows | groups} between unbounded preceding and ...
// (no order by) range
if ((m_frameExtent->frame1->bound == Frame::Bound::PRECEDING && !m_frameExtent->frame1->value) ||
(!m_order && m_frameExtent->unit == FrameExtent::Unit::RANGE))
Expand All @@ -673,12 +677,26 @@ bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const
{
impure->windowBlock.startPosition = position;
}
// groups between current row and ...
else if (m_frameExtent->unit == FrameExtent::Unit::GROUPS &&
m_frameExtent->frame1->bound == Frame::Bound::CURRENT_ROW)
{
impure->windowBlock.startPosition = locateFrameGroups(tdbb, request, impure,
m_frameExtent->frame1, nullptr, position, true);
}
// rows between <n> {preceding | following} and ...
else if (m_frameExtent->unit == FrameExtent::Unit::ROWS &&
m_frameExtent->frame1->value)
{
impure->windowBlock.startPosition = position + impure->startOffset.vlux_count;
}
// groups between <n> {preceding | following} and ...
else if (m_frameExtent->unit == FrameExtent::Unit::GROUPS &&
m_frameExtent->frame1->value)
{
impure->windowBlock.startPosition = locateFrameGroups(tdbb, request, impure,
m_frameExtent->frame1, &impure->startOffset, position, true);
}
// range between current row and ...
else if (m_frameExtent->unit == FrameExtent::Unit::RANGE &&
m_frameExtent->frame1->bound == Frame::Bound::CURRENT_ROW)
Expand All @@ -703,7 +721,7 @@ bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const
if (m_frameExtent->frame2->value && !(m_invariantOffsets & 0x2))
getFrameValue(tdbb, request, m_frameExtent->frame2, &impure->endOffset);

// {range | rows} between ... and unbounded following
// {range | rows | groups} between ... and unbounded following
// (no order by) range
if ((m_frameExtent->frame2->bound == Frame::Bound::FOLLOWING && !m_frameExtent->frame2->value) ||
(!m_order && m_frameExtent->unit == FrameExtent::Unit::RANGE))
Expand All @@ -716,12 +734,26 @@ bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const
{
impure->windowBlock.endPosition = position;
}
// groups between ... and current row
else if (m_frameExtent->unit == FrameExtent::Unit::GROUPS &&
m_frameExtent->frame2->bound == Frame::Bound::CURRENT_ROW)
{
impure->windowBlock.endPosition = locateFrameGroups(tdbb, request, impure,
m_frameExtent->frame2, nullptr, position, false);
}
// rows between ... and <n> {preceding | following}
else if (m_frameExtent->unit == FrameExtent::Unit::ROWS &&
m_frameExtent->frame2->value)
{
impure->windowBlock.endPosition = position + impure->endOffset.vlux_count;
}
// groups between ... and <n> {preceding | following}
else if (m_frameExtent->unit == FrameExtent::Unit::GROUPS &&
m_frameExtent->frame2->value)
{
impure->windowBlock.endPosition = locateFrameGroups(tdbb, request, impure,
m_frameExtent->frame2, &impure->endOffset, position, false);
}
// range between ... and current row
else if (m_frameExtent->unit == FrameExtent::Unit::RANGE &&
m_frameExtent->frame2->bound == Frame::Bound::CURRENT_ROW)
Expand Down Expand Up @@ -765,11 +797,14 @@ bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const
if (m_exclusion == Exclusion::NO_OTHERS &&
((m_frameExtent->frame1->bound == Frame::Bound::PRECEDING && !m_frameExtent->frame1->value &&
m_frameExtent->frame2->bound == Frame::Bound::FOLLOWING && !m_frameExtent->frame2->value) ||
(m_frameExtent->unit == FrameExtent::Unit::RANGE && !m_order)))
((m_frameExtent->unit == FrameExtent::Unit::RANGE ||
m_frameExtent->unit == FrameExtent::Unit::GROUPS) && !m_order)))
{
impure->rangePending = MAX(0, impure->windowBlock.endPosition - position);
}
else if (m_exclusion == Exclusion::NO_OTHERS && m_frameExtent->unit == FrameExtent::Unit::RANGE)
else if (m_exclusion == Exclusion::NO_OTHERS &&
(m_frameExtent->unit == FrameExtent::Unit::RANGE ||
m_frameExtent->unit == FrameExtent::Unit::GROUPS))
{
SINT64 rangePos = position;
cacheValues(tdbb, request, &m_order->expressions, impure->orderValues,
Expand Down Expand Up @@ -1006,7 +1041,8 @@ void WindowedStream::WindowStream::getFrameValue(thread_db* tdbb, Request* reque
error = true;
else
{
if (m_frameExtent->unit == FrameExtent::Unit::ROWS)
if (m_frameExtent->unit == FrameExtent::Unit::ROWS ||
m_frameExtent->unit == FrameExtent::Unit::GROUPS)
{
// Purposedly used 32-bit here. So long distance will complicate things for no gain.
impureValue->vlux_count = MOV_get_long(tdbb, desc, 0);
Expand Down Expand Up @@ -1044,6 +1080,14 @@ WindowedStream::WindowStream::Block WindowedStream::WindowStream::getPeerBlock(
return peerBlock;
}

if ((SINT64) m_next->getPosition(request) != position + 1)
{
m_next->locate(tdbb, position);

if (!m_next->getRecord(tdbb))
fb_assert(false);
}

cacheValues(tdbb, request, &m_order->expressions, impure->orderValues, DummyAdjustFunctor());

while (peerBlock.startPosition > impure->partitionBlock.startPosition)
Expand Down Expand Up @@ -1145,6 +1189,62 @@ bool WindowedStream::WindowStream::isExcluded(SINT64 position, const Block& excl
position >= exclusion2.startPosition && position <= exclusion2.endPosition);
}

SINT64 WindowedStream::WindowStream::locateFrameGroups(thread_db* tdbb, Request* request,
Impure* impure, const Frame* frame, const impure_value_ex* offsetValue, SINT64 position,
bool startFrame) const
{
SINT64 offset = 0;

if (offsetValue)
{
offset = MOV_get_long(tdbb, &offsetValue->vlu_desc, 0);

if (frame->bound == Frame::Bound::PRECEDING)
offset = -offset;
}

Block groupBlock = getPeerBlock(tdbb, request, impure, position);

auto restoreAndReturn = [&] (SINT64 result)
{
m_next->locate(tdbb, position);

if (!m_next->getRecord(tdbb))
fb_assert(false);

return result;
};

if (offset < 0)
{
for (SINT64 pending = -offset; pending > 0; --pending)
{
if (groupBlock.startPosition <= impure->partitionBlock.startPosition)
{
return restoreAndReturn(startFrame ?
impure->partitionBlock.startPosition : impure->partitionBlock.startPosition - 1);
}

groupBlock = getPeerBlock(tdbb, request, impure, groupBlock.startPosition - 1);
}
}
else if (offset > 0)
{
for (SINT64 pending = offset; pending > 0; --pending)
{
if (groupBlock.endPosition >= impure->partitionBlock.endPosition)
{
return restoreAndReturn(startFrame ?
impure->partitionBlock.endPosition + 1 : impure->partitionBlock.endPosition);
}

groupBlock = getPeerBlock(tdbb, request, impure, groupBlock.endPosition + 1);
}
}

return restoreAndReturn(startFrame ? groupBlock.startPosition : groupBlock.endPosition);
}

SINT64 WindowedStream::WindowStream::locateFrameRange(thread_db* tdbb, Request* request, Impure* impure,
const Frame* frame, const dsc* offsetDesc, SINT64 position) const
{
Expand Down
Loading