Skip to content

Commit 1a6abab

Browse files
committed
Feature #2058 - Window Function: frame exclude clause
1 parent b66bf5b commit 1a6abab

6 files changed

Lines changed: 359 additions & 73 deletions

File tree

doc/sql.extensions/README.window_functions.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ By the SQL specification, window functions (also know as analytical functions) a
44

55
That sort of functions are used with the `OVER` clause. Window functions may appear only in the select list or the `ORDER BY` clause of a query.
66

7-
Additional to the `OVER` clause, Firebird window functions may use partitions, order and frames (FB 4.0).
7+
Additional to the `OVER` clause, Firebird window functions may use partitions, order, frames (FB 4.0) and frame exclusions (FB 6.0).
88

99
Syntax:
1010

@@ -14,7 +14,7 @@ Syntax:
1414
OVER {<window specification> | <existing window name>}
1515
1616
<window specification> ::=
17-
([<existing window name>] [<window partition>] [<window order>] [<window frame>])
17+
([<existing window name>] [<window partition>] [<window order>] [<window frame>] [<window frame exclusion>])
1818
1919
<window partition> ::=
2020
PARTITION BY <expr> [, <expr> ...]
@@ -35,6 +35,9 @@ Syntax:
3535
BETWEEN {UNBOUNDED PRECEDING | <expr> PRECEDING | <expr> FOLLOWING | CURRENT ROW} AND
3636
{UNBOUNDED FOLLOWING | <expr> PRECEDING | <expr> FOLLOWING | CURRENT ROW}
3737
38+
<window frame exclusion> ::=
39+
EXCLUDE {NO OTHERS | CURRENT ROW | GROUP | TIES}
40+
3841
<direction> ::=
3942
{ASC | DESC}
4043
@@ -279,6 +282,28 @@ With `ROWS`, order expressions is not limited by number or types. In this case,
279282

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

285+
The optional frame exclusion clause (FB 6.0) removes rows from the frame after its bounds have been evaluated. `EXCLUDE NO OTHERS` is the default and keeps the frame unchanged.
286+
287+
`EXCLUDE CURRENT ROW` removes only the current row from the frame.
288+
289+
`EXCLUDE GROUP` removes the current row and all its peers. Peers are rows with the same `ORDER BY` values as the current row. If there is no `ORDER BY`, all rows in the partition are peers.
290+
291+
`EXCLUDE TIES` removes the peers of the current row, but keeps the current row itself. If there is no `ORDER BY`, all other rows in the partition are removed from the frame.
292+
293+
For example, with duplicate salaries, the following query sums the full ordered partition except the current salary peer group:
294+
295+
```sql
296+
select
297+
id,
298+
salary,
299+
sum(salary) over (
300+
order by salary
301+
rows between unbounded preceding and unbounded following
302+
exclude group
303+
) sum_other_salaries
304+
from employee
305+
order by salary;
306+
```
282307

283308

284309
When `ORDER BY` window clause is used but frame clause is omitted, it defaults to `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`. This fact makes the query below to produce "weird" behaviour for the "sum_salary" column. It sums from the partition start to the current key, instead of sum the whole partition.
@@ -346,9 +371,9 @@ select
346371
| 5 | 10.00 | 3 |
347372
| 2 | 12.00 | 1 |
348373

349-
Some window functions discard frames. `ROW_NUMBER`, `LAG` and `LEAD` always work as `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`. And `DENSE_RANK`, `RANK`, `PERCENT_RANK` and `CUME_DIST` always work as `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`.
374+
Some window functions discard frames and frame exclusions. `ROW_NUMBER`, `LAG` and `LEAD` always work as `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`. And `DENSE_RANK`, `RANK`, `PERCENT_RANK` and `CUME_DIST` always work as `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`.
350375

351-
`FIRST_VALUE`, `LAST_VALUE` and `NTH_VALUE` respect frames, but the `RANGE` unit works identically as `ROWS`.
376+
`FIRST_VALUE`, `LAST_VALUE` and `NTH_VALUE` respect frames and frame exclusions, but the `RANGE` unit works identically as `ROWS`.
352377

353378
## 6. Named windows (FB 4.0)
354379

src/dsql/ExprNodes.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9644,12 +9644,14 @@ ValueExprNode* OverNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
96449644
const AggNode* aggNode = nodeAs<AggNode>(node->aggExpr);
96459645

96469646
if (node->window &&
9647-
node->window->extent &&
96489647
aggNode &&
9648+
(node->window->extent || node->window->exclusion != WindowClause::Exclusion::NO_OTHERS) &&
96499649
(aggNode->getCapabilities() & AggNode::CAP_RESPECTS_WINDOW_FRAME) !=
96509650
AggNode::CAP_RESPECTS_WINDOW_FRAME)
96519651
{
9652-
node->window->extent = WindowClause::FrameExtent::createDefault(dsqlScratch->getPool());
9652+
if (node->window->extent)
9653+
node->window->extent = WindowClause::FrameExtent::createDefault(dsqlScratch->getPool());
9654+
96539655
node->window->exclusion = WindowClause::Exclusion::NO_OTHERS;
96549656
}
96559657

src/dsql/WinNodes.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ void FirstValueWinNode::aggInit(thread_db* tdbb, Request* request) const
436436

437437
dsc* FirstValueWinNode::winPass(thread_db* tdbb, Request* request, SlidingWindow* window) const
438438
{
439-
if (!window->moveWithinFrame(-window->getInFrameOffset()))
439+
if (!window->moveToFrameStart())
440440
return nullptr;
441441

442442
dsc* desc = EVL_expr(tdbb, request, arg);
@@ -497,7 +497,7 @@ void LastValueWinNode::aggInit(thread_db* tdbb, Request* request) const
497497

498498
dsc* LastValueWinNode::winPass(thread_db* tdbb, Request* request, SlidingWindow* window) const
499499
{
500-
if (!window->moveWithinFrame(window->getFrameEnd() - window->getRecordPosition()))
500+
if (!window->moveToFrameEnd())
501501
return nullptr;
502502

503503
dsc* desc = EVL_expr(tdbb, request, arg);
@@ -587,11 +587,11 @@ dsc* NthValueWinNode::winPass(thread_db* tdbb, Request* request, SlidingWindow*
587587
const SLONG fromPos = desc ? MOV_get_long(tdbb, desc, 0) : FROM_FIRST;
588588

589589
if (fromPos == FROM_FIRST)
590-
records -= window->getInFrameOffset() + 1;
590+
--records;
591591
else
592-
records = window->getFrameEnd() - window->getRecordPosition() - records + 1;
592+
records = window->getEffectiveFrameSize() - records;
593593

594-
if (!window->moveWithinFrame(records))
594+
if (!window->moveToFrameOffset(records))
595595
return nullptr;
596596

597597
desc = EVL_expr(tdbb, request, arg);

src/jrd/RecordSourceNodes.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2656,11 +2656,6 @@ void WindowSourceNode::parseWindow(thread_db* tdbb, CompilerScratch* csb)
26562656
break;
26572657

26582658
case blr_window_win_exclusion:
2659-
//// TODO: CORE-5338 - write code for execution.
2660-
PAR_error(csb,
2661-
Arg::Gds(isc_wish_list) <<
2662-
Arg::Gds(isc_random) << "window EXCLUDE clause");
2663-
26642659
window.exclusion = (WindowClause::Exclusion) csb->csb_blr_reader.getByte();
26652660

26662661
switch (window.exclusion)

src/jrd/recsrc/RecordSource.h

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -770,41 +770,43 @@ namespace Jrd
770770
{
771771
public:
772772
SlidingWindow(thread_db* aTdbb, const BaseBufferedStream* aStream, Request* request,
773-
FB_UINT64 aPartitionStart, FB_UINT64 aPartitionEnd,
774-
FB_UINT64 aFrameStart, FB_UINT64 aFrameEnd);
773+
SINT64 aPartitionStart, SINT64 aPartitionEnd,
774+
SINT64 aFrameStart, SINT64 aFrameEnd,
775+
SINT64 aExclusionStart1, SINT64 aExclusionEnd1,
776+
SINT64 aExclusionStart2, SINT64 aExclusionEnd2);
775777
~SlidingWindow();
776778

777-
FB_UINT64 getPartitionStart() const
779+
SINT64 getPartitionStart() const
778780
{
779781
return partitionStart;
780782
}
781783

782-
FB_UINT64 getPartitionEnd() const
784+
SINT64 getPartitionEnd() const
783785
{
784786
return partitionEnd;
785787
}
786788

787-
FB_UINT64 getPartitionSize() const
789+
SINT64 getPartitionSize() const
788790
{
789791
return partitionEnd - partitionStart + 1;
790792
}
791793

792-
FB_UINT64 getFrameStart() const
794+
SINT64 getFrameStart() const
793795
{
794796
return frameStart;
795797
}
796798

797-
FB_UINT64 getFrameEnd() const
799+
SINT64 getFrameEnd() const
798800
{
799801
return frameEnd;
800802
}
801803

802-
FB_UINT64 getFrameSize() const
804+
SINT64 getFrameSize() const
803805
{
804-
return frameEnd - frameStart + 1;
806+
return getEffectiveFrameSize();
805807
}
806808

807-
FB_UINT64 getRecordPosition() const
809+
SINT64 getRecordPosition() const
808810
{
809811
return savedPosition;
810812
}
@@ -825,15 +827,37 @@ namespace Jrd
825827

826828
bool moveWithinPartition(SINT64 delta);
827829
bool moveWithinFrame(SINT64 delta);
830+
bool moveToFrameStart();
831+
bool moveToFrameEnd();
832+
bool moveToFrameOffset(SINT64 offset);
833+
SINT64 getEffectiveFrameSize() const;
834+
835+
private:
836+
bool hasFrame() const
837+
{
838+
return frameStart <= frameEnd && frameStart >= partitionStart && frameEnd <= partitionEnd;
839+
}
840+
841+
bool isExcluded(SINT64 position) const
842+
{
843+
return (position >= exclusionStart1 && position <= exclusionEnd1) ||
844+
(position >= exclusionStart2 && position <= exclusionEnd2);
845+
}
846+
847+
bool moveToFramePosition(SINT64 position);
828848

829849
private:
830850
thread_db* tdbb;
831851
const BaseBufferedStream* const stream;
832-
FB_UINT64 partitionStart;
833-
FB_UINT64 partitionEnd;
834-
FB_UINT64 frameStart;
835-
FB_UINT64 frameEnd;
836-
FB_UINT64 savedPosition;
852+
SINT64 partitionStart;
853+
SINT64 partitionEnd;
854+
SINT64 frameStart;
855+
SINT64 frameEnd;
856+
SINT64 exclusionStart1;
857+
SINT64 exclusionEnd1;
858+
SINT64 exclusionStart2;
859+
SINT64 exclusionEnd2;
860+
SINT64 savedPosition;
837861
bool moved = false;
838862
};
839863

@@ -1032,6 +1056,12 @@ namespace Jrd
10321056
void getFrameValue(thread_db* tdbb, Request* request,
10331057
const Frame* frame, impure_value_ex* impureValue) const;
10341058

1059+
Block getPeerBlock(thread_db* tdbb, Request* request, Impure* impure,
1060+
SINT64 position) const;
1061+
void getExclusionBlocks(thread_db* tdbb, Request* request, Impure* impure,
1062+
SINT64 position, Block* exclusion1, Block* exclusion2) const;
1063+
bool isExcluded(SINT64 position, const Block& exclusion1, const Block& exclusion2) const;
1064+
10351065
SINT64 locateFrameRange(thread_db* tdbb, Request* request, Impure* impure,
10361066
const Frame* frame, const dsc* offsetDesc, SINT64 position) const;
10371067

0 commit comments

Comments
 (0)