PostgreSQL Source Code git master
windowfuncs.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * windowfuncs.c
4 * Standard window functions defined in SQL spec.
5 *
6 * Portions Copyright (c) 2000-2025, PostgreSQL Global Development Group
7 *
8 *
9 * IDENTIFICATION
10 * src/backend/utils/adt/windowfuncs.c
11 *
12 *-------------------------------------------------------------------------
13 */
14#include "postgres.h"
15
16#include "nodes/parsenodes.h"
17#include "nodes/supportnodes.h"
18#include "utils/fmgrprotos.h"
19#include "windowapi.h"
20
21/*
22 * ranking process information
23 */
24typedef struct rank_context
25{
26 int64 rank; /* current rank */
28
29/*
30 * ntile process information
31 */
32typedef struct
33{
34 int32 ntile; /* current result */
35 int64 rows_per_bucket; /* row number of current bucket */
36 int64 boundary; /* how many rows should be in the bucket */
37 int64 remainder; /* (total rows) % (bucket num) */
39
40static bool rank_up(WindowObject winobj);
42 bool forward, bool withoffset, bool withdefault);
43
44
45/*
46 * utility routine for *_rank functions.
47 */
48static bool
50{
51 bool up = false; /* should rank increase? */
52 int64 curpos = WinGetCurrentPosition(winobj);
53 rank_context *context;
54
55 context = (rank_context *)
57
58 if (context->rank == 0)
59 {
60 /* first call: rank of first row is always 1 */
61 Assert(curpos == 0);
62 context->rank = 1;
63 }
64 else
65 {
66 Assert(curpos > 0);
67 /* do current and prior tuples match by ORDER BY clause? */
68 if (!WinRowsArePeers(winobj, curpos - 1, curpos))
69 up = true;
70 }
71
72 /* We can advance the mark, but only *after* access to prior row */
73 WinSetMarkPosition(winobj, curpos);
74
75 return up;
76}
77
78
79/*
80 * row_number
81 * just increment up from 1 until current partition finishes.
82 */
85{
87 int64 curpos = WinGetCurrentPosition(winobj);
88
89 WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
90 WinSetMarkPosition(winobj, curpos);
91 PG_RETURN_INT64(curpos + 1);
92}
93
94/*
95 * window_row_number_support
96 * prosupport function for window_row_number()
97 */
100{
101 Node *rawreq = (Node *) PG_GETARG_POINTER(0);
102
104 {
106
107 /* row_number() is monotonically increasing */
110 }
111
113 {
115
116 /*
117 * The frame options can always become "ROWS BETWEEN UNBOUNDED
118 * PRECEDING AND CURRENT ROW". row_number() always just increments by
119 * 1 with each row in the partition. Using ROWS instead of RANGE
120 * saves effort checking peer rows during execution.
121 */
126
128 }
129
130 PG_RETURN_POINTER(NULL);
131}
132
133/*
134 * rank
135 * Rank changes when key columns change.
136 * The new rank number is the current row number.
137 */
138Datum
140{
142 rank_context *context;
143 bool up;
144
145 WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
146 up = rank_up(winobj);
147 context = (rank_context *)
149 if (up)
150 context->rank = WinGetCurrentPosition(winobj) + 1;
151
152 PG_RETURN_INT64(context->rank);
153}
154
155/*
156 * window_rank_support
157 * prosupport function for window_rank()
158 */
159Datum
161{
162 Node *rawreq = (Node *) PG_GETARG_POINTER(0);
163
165 {
167
168 /* rank() is monotonically increasing */
171 }
172
174 {
176
177 /*
178 * rank() is coded in such a way that it returns "(COUNT (*) OVER
179 * (<opt> RANGE UNBOUNDED PRECEDING) - COUNT (*) OVER (<opt> RANGE
180 * CURRENT ROW) + 1)" regardless of the frame options. We'll set the
181 * frame options to "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
182 * so they agree with what window_row_number_support() optimized the
183 * frame options to be. Using ROWS instead of RANGE saves from doing
184 * peer row checks during execution.
185 */
190
192 }
193
194 PG_RETURN_POINTER(NULL);
195}
196
197/*
198 * dense_rank
199 * Rank increases by 1 when key columns change.
200 */
201Datum
203{
205 rank_context *context;
206 bool up;
207
208 WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
209 up = rank_up(winobj);
210 context = (rank_context *)
212 if (up)
213 context->rank++;
214
215 PG_RETURN_INT64(context->rank);
216}
217
218/*
219 * window_dense_rank_support
220 * prosupport function for window_dense_rank()
221 */
222Datum
224{
225 Node *rawreq = (Node *) PG_GETARG_POINTER(0);
226
228 {
230
231 /* dense_rank() is monotonically increasing */
234 }
235
237 {
239
240 /*
241 * dense_rank() is unaffected by the frame options. Here we set the
242 * frame options to match what's done in row_number's support
243 * function. Using ROWS instead of RANGE (the default) saves the
244 * executor from having to check for peer rows.
245 */
250
252 }
253
254 PG_RETURN_POINTER(NULL);
255}
256
257/*
258 * percent_rank
259 * return fraction between 0 and 1 inclusive,
260 * which is described as (RK - 1) / (NR - 1), where RK is the current row's
261 * rank and NR is the total number of rows, per spec.
262 */
263Datum
265{
267 rank_context *context;
268 bool up;
269 int64 totalrows = WinGetPartitionRowCount(winobj);
270
271 Assert(totalrows > 0);
272 WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
273
274 up = rank_up(winobj);
275 context = (rank_context *)
277 if (up)
278 context->rank = WinGetCurrentPosition(winobj) + 1;
279
280 /* return zero if there's only one row, per spec */
281 if (totalrows <= 1)
282 PG_RETURN_FLOAT8(0.0);
283
284 PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1));
285}
286
287/*
288 * window_percent_rank_support
289 * prosupport function for window_percent_rank()
290 */
291Datum
293{
294 Node *rawreq = (Node *) PG_GETARG_POINTER(0);
295
297 {
299
300 /* percent_rank() is monotonically increasing */
303 }
304
306 {
308
309 /*
310 * percent_rank() is unaffected by the frame options. Here we set the
311 * frame options to match what's done in row_number's support
312 * function. Using ROWS instead of RANGE (the default) saves the
313 * executor from having to check for peer rows.
314 */
319
321 }
322
323 PG_RETURN_POINTER(NULL);
324}
325
326
327/*
328 * cume_dist
329 * return fraction between 0 and 1 inclusive,
330 * which is described as NP / NR, where NP is the number of rows preceding or
331 * peers to the current row, and NR is the total number of rows, per spec.
332 */
333Datum
335{
337 rank_context *context;
338 bool up;
339 int64 totalrows = WinGetPartitionRowCount(winobj);
340
341 Assert(totalrows > 0);
342 WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
343
344 up = rank_up(winobj);
345 context = (rank_context *)
347 if (up || context->rank == 1)
348 {
349 /*
350 * The current row is not peer to prior row or is just the first, so
351 * count up the number of rows that are peer to the current.
352 */
353 int64 row;
354
355 context->rank = WinGetCurrentPosition(winobj) + 1;
356
357 /*
358 * start from current + 1
359 */
360 for (row = context->rank; row < totalrows; row++)
361 {
362 if (!WinRowsArePeers(winobj, row - 1, row))
363 break;
364 context->rank++;
365 }
366 }
367
368 PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
369}
370
371/*
372 * window_cume_dist_support
373 * prosupport function for window_cume_dist()
374 */
375Datum
377{
378 Node *rawreq = (Node *) PG_GETARG_POINTER(0);
379
381 {
383
384 /* cume_dist() is monotonically increasing */
387 }
388
390 {
392
393 /*
394 * cume_dist() is unaffected by the frame options. Here we set the
395 * frame options to match what's done in row_number's support
396 * function. Using ROWS instead of RANGE (the default) saves the
397 * executor from having to check for peer rows.
398 */
403
405 }
406
407 PG_RETURN_POINTER(NULL);
408}
409
410/*
411 * ntile
412 * compute an exact numeric value with scale 0 (zero),
413 * ranging from 1 (one) to n, per spec.
414 */
415Datum
417{
419 ntile_context *context;
420
421 WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
422 context = (ntile_context *)
424
425 if (context->ntile == 0)
426 {
427 /* first call */
428 int64 total;
429 int32 nbuckets;
430 bool isnull;
431
432 total = WinGetPartitionRowCount(winobj);
433 nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull));
434
435 /*
436 * per spec: If NT is the null value, then the result is the null
437 * value.
438 */
439 if (isnull)
441
442 /*
443 * per spec: If NT is less than or equal to 0 (zero), then an
444 * exception condition is raised.
445 */
446 if (nbuckets <= 0)
448 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
449 errmsg("argument of ntile must be greater than zero")));
450
451 context->ntile = 1;
452 context->rows_per_bucket = 0;
453 context->boundary = total / nbuckets;
454 if (context->boundary <= 0)
455 context->boundary = 1;
456 else
457 {
458 /*
459 * If the total number is not divisible, add 1 row to leading
460 * buckets.
461 */
462 context->remainder = total % nbuckets;
463 if (context->remainder != 0)
464 context->boundary++;
465 }
466 }
467
468 context->rows_per_bucket++;
469 if (context->boundary < context->rows_per_bucket)
470 {
471 /* ntile up */
472 if (context->remainder != 0 && context->ntile == context->remainder)
473 {
474 context->remainder = 0;
475 context->boundary -= 1;
476 }
477 context->ntile += 1;
478 context->rows_per_bucket = 1;
479 }
480
481 PG_RETURN_INT32(context->ntile);
482}
483
484/*
485 * window_ntile_support
486 * prosupport function for window_ntile()
487 */
488Datum
490{
491 Node *rawreq = (Node *) PG_GETARG_POINTER(0);
492
494 {
496
497 /*
498 * ntile() is monotonically increasing as the number of buckets cannot
499 * change after the first call
500 */
503 }
504
506 {
508
509 /*
510 * ntile() is unaffected by the frame options. Here we set the frame
511 * options to match what's done in row_number's support function.
512 * Using ROWS instead of RANGE (the default) saves the executor from
513 * having to check for peer rows.
514 */
519
521 }
522
523 PG_RETURN_POINTER(NULL);
524}
525
526/*
527 * leadlag_common
528 * common operation of lead() and lag()
529 * For lead() forward is true, whereas for lag() it is false.
530 * withoffset indicates we have an offset second argument.
531 * withdefault indicates we have a default third argument.
532 */
533static Datum
535 bool forward, bool withoffset, bool withdefault)
536{
538 int32 offset;
539 bool const_offset;
540 Datum result;
541 bool isnull;
542 bool isout;
543
544 WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
545 if (withoffset)
546 {
547 offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
548 if (isnull)
550 const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
551 }
552 else
553 {
554 offset = 1;
555 const_offset = true;
556 }
557
558 result = WinGetFuncArgInPartition(winobj, 0,
559 (forward ? offset : -offset),
561 const_offset,
562 &isnull, &isout);
563
564 if (isout)
565 {
566 /*
567 * target row is out of the partition; supply default value if
568 * provided. otherwise it'll stay NULL
569 */
570 if (withdefault)
571 result = WinGetFuncArgCurrent(winobj, 2, &isnull);
572 }
573
574 if (isnull)
576
577 PG_RETURN_DATUM(result);
578}
579
580/*
581 * lag
582 * returns the value of VE evaluated on a row that is 1
583 * row before the current row within a partition,
584 * per spec.
585 */
586Datum
588{
589 return leadlag_common(fcinfo, false, false, false);
590}
591
592/*
593 * lag_with_offset
594 * returns the value of VE evaluated on a row that is OFFSET
595 * rows before the current row within a partition,
596 * per spec.
597 */
598Datum
600{
601 return leadlag_common(fcinfo, false, true, false);
602}
603
604/*
605 * lag_with_offset_and_default
606 * same as lag_with_offset but accepts default value
607 * as its third argument.
608 */
609Datum
611{
612 return leadlag_common(fcinfo, false, true, true);
613}
614
615/*
616 * lead
617 * returns the value of VE evaluated on a row that is 1
618 * row after the current row within a partition,
619 * per spec.
620 */
621Datum
623{
624 return leadlag_common(fcinfo, true, false, false);
625}
626
627/*
628 * lead_with_offset
629 * returns the value of VE evaluated on a row that is OFFSET
630 * number of rows after the current row within a partition,
631 * per spec.
632 */
633Datum
635{
636 return leadlag_common(fcinfo, true, true, false);
637}
638
639/*
640 * lead_with_offset_and_default
641 * same as lead_with_offset but accepts default value
642 * as its third argument.
643 */
644Datum
646{
647 return leadlag_common(fcinfo, true, true, true);
648}
649
650/*
651 * first_value
652 * return the value of VE evaluated on the first row of the
653 * window frame, per spec.
654 */
655Datum
657{
659 Datum result;
660 bool isnull;
661
662 WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
663 result = WinGetFuncArgInFrame(winobj, 0,
664 0, WINDOW_SEEK_HEAD, true,
665 &isnull, NULL);
666 if (isnull)
668
669 PG_RETURN_DATUM(result);
670}
671
672/*
673 * last_value
674 * return the value of VE evaluated on the last row of the
675 * window frame, per spec.
676 */
677Datum
679{
681 Datum result;
682 bool isnull;
683
684 WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
685 result = WinGetFuncArgInFrame(winobj, 0,
686 0, WINDOW_SEEK_TAIL, true,
687 &isnull, NULL);
688 if (isnull)
690
691 PG_RETURN_DATUM(result);
692}
693
694/*
695 * nth_value
696 * return the value of VE evaluated on the n-th row from the first
697 * row of the window frame, per spec.
698 */
699Datum
701{
703 bool const_offset;
704 Datum result;
705 bool isnull;
706 int32 nth;
707
708 WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
709 nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
710 if (isnull)
712 const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
713
714 if (nth <= 0)
716 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
717 errmsg("argument of nth_value must be greater than zero")));
718
719 result = WinGetFuncArgInFrame(winobj, 0,
720 nth - 1, WINDOW_SEEK_HEAD, const_offset,
721 &isnull, NULL);
722 if (isnull)
724
725 PG_RETURN_DATUM(result);
726}
int64_t int64
Definition: c.h:540
double float8
Definition: c.h:640
int32_t int32
Definition: c.h:539
int errcode(int sqlerrcode)
Definition: elog.c:863
int errmsg(const char *fmt,...)
Definition: elog.c:1080
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:150
bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum)
Definition: fmgr.c:1940
#define PG_RETURN_FLOAT8(x)
Definition: fmgr.h:367
#define PG_GETARG_POINTER(n)
Definition: fmgr.h:276
#define PG_RETURN_INT64(x)
Definition: fmgr.h:368
#define PG_RETURN_NULL()
Definition: fmgr.h:345
#define PG_RETURN_INT32(x)
Definition: fmgr.h:354
#define PG_RETURN_DATUM(x)
Definition: fmgr.h:353
#define PG_RETURN_POINTER(x)
Definition: fmgr.h:361
#define PG_FUNCTION_ARGS
Definition: fmgr.h:193
Assert(PointerIsAligned(start, uint64))
Datum WinGetFuncArgInPartition(WindowObject winobj, int argno, int relpos, int seektype, bool set_mark, bool *isnull, bool *isout)
void * WinGetPartitionLocalMemory(WindowObject winobj, Size sz)
Datum WinGetFuncArgInFrame(WindowObject winobj, int argno, int relpos, int seektype, bool set_mark, bool *isnull, bool *isout)
int64 WinGetCurrentPosition(WindowObject winobj)
bool WinRowsArePeers(WindowObject winobj, int64 pos1, int64 pos2)
void WinSetMarkPosition(WindowObject winobj, int64 markpos)
void WinCheckAndInitializeNullTreatment(WindowObject winobj, bool allowNullTreatment, FunctionCallInfo fcinfo)
Datum WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
int64 WinGetPartitionRowCount(WindowObject winobj)
#define IsA(nodeptr, _type_)
Definition: nodes.h:164
#define FRAMEOPTION_END_CURRENT_ROW
Definition: parsenodes.h:619
#define FRAMEOPTION_START_UNBOUNDED_PRECEDING
Definition: parsenodes.h:614
#define FRAMEOPTION_NONDEFAULT
Definition: parsenodes.h:609
#define FRAMEOPTION_ROWS
Definition: parsenodes.h:611
@ MONOTONICFUNC_INCREASING
Definition: plannodes.h:1819
uint64_t Datum
Definition: postgres.h:70
static int32 DatumGetInt32(Datum X)
Definition: postgres.h:212
FmgrInfo * flinfo
Definition: fmgr.h:87
Definition: nodes.h:135
MonotonicFunction monotonic
Definition: supportnodes.h:299
int64 rows_per_bucket
Definition: windowfuncs.c:35
int64 remainder
Definition: windowfuncs.c:37
int64 boundary
Definition: windowfuncs.c:36
#define WINDOW_SEEK_TAIL
Definition: windowapi.h:36
#define WINDOW_SEEK_HEAD
Definition: windowapi.h:35
#define PG_WINDOW_OBJECT()
Definition: windowapi.h:41
#define WINDOW_SEEK_CURRENT
Definition: windowapi.h:34
Datum window_lag_with_offset(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:599
static bool rank_up(WindowObject winobj)
Definition: windowfuncs.c:49
Datum window_dense_rank(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:202
Datum window_cume_dist_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:376
Datum window_last_value(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:678
Datum window_percent_rank(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:264
static Datum leadlag_common(FunctionCallInfo fcinfo, bool forward, bool withoffset, bool withdefault)
Definition: windowfuncs.c:534
Datum window_ntile(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:416
Datum window_row_number(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:84
Datum window_percent_rank_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:292
Datum window_first_value(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:656
Datum window_lead(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:622
Datum window_lead_with_offset(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:634
Datum window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:610
Datum window_lag(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:587
Datum window_row_number_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:99
Datum window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:645
Datum window_cume_dist(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:334
Datum window_rank(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:139
Datum window_ntile_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:489
struct rank_context rank_context
Datum window_dense_rank_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:223
Datum window_nth_value(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:700
Datum window_rank_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:160