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-2022, 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/supportnodes.h"
17 #include "utils/builtins.h"
18 #include "windowapi.h"
19 
20 /*
21  * ranking process information
22  */
23 typedef struct rank_context
24 {
25  int64 rank; /* current rank */
27 
28 /*
29  * ntile process information
30  */
31 typedef struct
32 {
33  int32 ntile; /* current result */
34  int64 rows_per_bucket; /* row number of current bucket */
35  int64 boundary; /* how many rows should be in the bucket */
36  int64 remainder; /* (total rows) % (bucket num) */
38 
39 static bool rank_up(WindowObject winobj);
41  bool forward, bool withoffset, bool withdefault);
42 
43 
44 /*
45  * utility routine for *_rank functions.
46  */
47 static bool
49 {
50  bool up = false; /* should rank increase? */
51  int64 curpos = WinGetCurrentPosition(winobj);
52  rank_context *context;
53 
54  context = (rank_context *)
56 
57  if (context->rank == 0)
58  {
59  /* first call: rank of first row is always 1 */
60  Assert(curpos == 0);
61  context->rank = 1;
62  }
63  else
64  {
65  Assert(curpos > 0);
66  /* do current and prior tuples match by ORDER BY clause? */
67  if (!WinRowsArePeers(winobj, curpos - 1, curpos))
68  up = true;
69  }
70 
71  /* We can advance the mark, but only *after* access to prior row */
72  WinSetMarkPosition(winobj, curpos);
73 
74  return up;
75 }
76 
77 
78 /*
79  * row_number
80  * just increment up from 1 until current partition finishes.
81  */
82 Datum
84 {
85  WindowObject winobj = PG_WINDOW_OBJECT();
86  int64 curpos = WinGetCurrentPosition(winobj);
87 
88  WinSetMarkPosition(winobj, curpos);
89  PG_RETURN_INT64(curpos + 1);
90 }
91 
92 /*
93  * window_row_number_support
94  * prosupport function for window_row_number()
95  */
96 Datum
98 {
99  Node *rawreq = (Node *) PG_GETARG_POINTER(0);
100 
101  if (IsA(rawreq, SupportRequestWFuncMonotonic))
102  {
104 
105  /* row_number() is monotonically increasing */
107  PG_RETURN_POINTER(req);
108  }
109 
110  PG_RETURN_POINTER(NULL);
111 }
112 
113 /*
114  * rank
115  * Rank changes when key columns change.
116  * The new rank number is the current row number.
117  */
118 Datum
120 {
121  WindowObject winobj = PG_WINDOW_OBJECT();
122  rank_context *context;
123  bool up;
124 
125  up = rank_up(winobj);
126  context = (rank_context *)
127  WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
128  if (up)
129  context->rank = WinGetCurrentPosition(winobj) + 1;
130 
131  PG_RETURN_INT64(context->rank);
132 }
133 
134 /*
135  * window_rank_support
136  * prosupport function for window_rank()
137  */
138 Datum
140 {
141  Node *rawreq = (Node *) PG_GETARG_POINTER(0);
142 
143  if (IsA(rawreq, SupportRequestWFuncMonotonic))
144  {
146 
147  /* rank() is monotonically increasing */
149  PG_RETURN_POINTER(req);
150  }
151 
152  PG_RETURN_POINTER(NULL);
153 }
154 
155 /*
156  * dense_rank
157  * Rank increases by 1 when key columns change.
158  */
159 Datum
161 {
162  WindowObject winobj = PG_WINDOW_OBJECT();
163  rank_context *context;
164  bool up;
165 
166  up = rank_up(winobj);
167  context = (rank_context *)
168  WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
169  if (up)
170  context->rank++;
171 
172  PG_RETURN_INT64(context->rank);
173 }
174 
175 /*
176  * window_dense_rank_support
177  * prosupport function for window_dense_rank()
178  */
179 Datum
181 {
182  Node *rawreq = (Node *) PG_GETARG_POINTER(0);
183 
184  if (IsA(rawreq, SupportRequestWFuncMonotonic))
185  {
187 
188  /* dense_rank() is monotonically increasing */
190  PG_RETURN_POINTER(req);
191  }
192 
193  PG_RETURN_POINTER(NULL);
194 }
195 
196 /*
197  * percent_rank
198  * return fraction between 0 and 1 inclusive,
199  * which is described as (RK - 1) / (NR - 1), where RK is the current row's
200  * rank and NR is the total number of rows, per spec.
201  */
202 Datum
204 {
205  WindowObject winobj = PG_WINDOW_OBJECT();
206  rank_context *context;
207  bool up;
208  int64 totalrows = WinGetPartitionRowCount(winobj);
209 
210  Assert(totalrows > 0);
211 
212  up = rank_up(winobj);
213  context = (rank_context *)
214  WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
215  if (up)
216  context->rank = WinGetCurrentPosition(winobj) + 1;
217 
218  /* return zero if there's only one row, per spec */
219  if (totalrows <= 1)
220  PG_RETURN_FLOAT8(0.0);
221 
222  PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1));
223 }
224 
225 /*
226  * cume_dist
227  * return fraction between 0 and 1 inclusive,
228  * which is described as NP / NR, where NP is the number of rows preceding or
229  * peers to the current row, and NR is the total number of rows, per spec.
230  */
231 Datum
233 {
234  WindowObject winobj = PG_WINDOW_OBJECT();
235  rank_context *context;
236  bool up;
237  int64 totalrows = WinGetPartitionRowCount(winobj);
238 
239  Assert(totalrows > 0);
240 
241  up = rank_up(winobj);
242  context = (rank_context *)
243  WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
244  if (up || context->rank == 1)
245  {
246  /*
247  * The current row is not peer to prior row or is just the first, so
248  * count up the number of rows that are peer to the current.
249  */
250  int64 row;
251 
252  context->rank = WinGetCurrentPosition(winobj) + 1;
253 
254  /*
255  * start from current + 1
256  */
257  for (row = context->rank; row < totalrows; row++)
258  {
259  if (!WinRowsArePeers(winobj, row - 1, row))
260  break;
261  context->rank++;
262  }
263  }
264 
265  PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
266 }
267 
268 /*
269  * ntile
270  * compute an exact numeric value with scale 0 (zero),
271  * ranging from 1 (one) to n, per spec.
272  */
273 Datum
275 {
276  WindowObject winobj = PG_WINDOW_OBJECT();
277  ntile_context *context;
278 
279  context = (ntile_context *)
280  WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
281 
282  if (context->ntile == 0)
283  {
284  /* first call */
285  int64 total;
286  int32 nbuckets;
287  bool isnull;
288 
289  total = WinGetPartitionRowCount(winobj);
290  nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull));
291 
292  /*
293  * per spec: If NT is the null value, then the result is the null
294  * value.
295  */
296  if (isnull)
297  PG_RETURN_NULL();
298 
299  /*
300  * per spec: If NT is less than or equal to 0 (zero), then an
301  * exception condition is raised.
302  */
303  if (nbuckets <= 0)
304  ereport(ERROR,
305  (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
306  errmsg("argument of ntile must be greater than zero")));
307 
308  context->ntile = 1;
309  context->rows_per_bucket = 0;
310  context->boundary = total / nbuckets;
311  if (context->boundary <= 0)
312  context->boundary = 1;
313  else
314  {
315  /*
316  * If the total number is not divisible, add 1 row to leading
317  * buckets.
318  */
319  context->remainder = total % nbuckets;
320  if (context->remainder != 0)
321  context->boundary++;
322  }
323  }
324 
325  context->rows_per_bucket++;
326  if (context->boundary < context->rows_per_bucket)
327  {
328  /* ntile up */
329  if (context->remainder != 0 && context->ntile == context->remainder)
330  {
331  context->remainder = 0;
332  context->boundary -= 1;
333  }
334  context->ntile += 1;
335  context->rows_per_bucket = 1;
336  }
337 
338  PG_RETURN_INT32(context->ntile);
339 }
340 
341 /*
342  * leadlag_common
343  * common operation of lead() and lag()
344  * For lead() forward is true, whereas for lag() it is false.
345  * withoffset indicates we have an offset second argument.
346  * withdefault indicates we have a default third argument.
347  */
348 static Datum
350  bool forward, bool withoffset, bool withdefault)
351 {
352  WindowObject winobj = PG_WINDOW_OBJECT();
353  int32 offset;
354  bool const_offset;
355  Datum result;
356  bool isnull;
357  bool isout;
358 
359  if (withoffset)
360  {
361  offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
362  if (isnull)
363  PG_RETURN_NULL();
364  const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
365  }
366  else
367  {
368  offset = 1;
369  const_offset = true;
370  }
371 
372  result = WinGetFuncArgInPartition(winobj, 0,
373  (forward ? offset : -offset),
375  const_offset,
376  &isnull, &isout);
377 
378  if (isout)
379  {
380  /*
381  * target row is out of the partition; supply default value if
382  * provided. otherwise it'll stay NULL
383  */
384  if (withdefault)
385  result = WinGetFuncArgCurrent(winobj, 2, &isnull);
386  }
387 
388  if (isnull)
389  PG_RETURN_NULL();
390 
391  PG_RETURN_DATUM(result);
392 }
393 
394 /*
395  * lag
396  * returns the value of VE evaluated on a row that is 1
397  * row before the current row within a partition,
398  * per spec.
399  */
400 Datum
402 {
403  return leadlag_common(fcinfo, false, false, false);
404 }
405 
406 /*
407  * lag_with_offset
408  * returns the value of VE evaluated on a row that is OFFSET
409  * rows before the current row within a partition,
410  * per spec.
411  */
412 Datum
414 {
415  return leadlag_common(fcinfo, false, true, false);
416 }
417 
418 /*
419  * lag_with_offset_and_default
420  * same as lag_with_offset but accepts default value
421  * as its third argument.
422  */
423 Datum
425 {
426  return leadlag_common(fcinfo, false, true, true);
427 }
428 
429 /*
430  * lead
431  * returns the value of VE evaluated on a row that is 1
432  * row after the current row within a partition,
433  * per spec.
434  */
435 Datum
437 {
438  return leadlag_common(fcinfo, true, false, false);
439 }
440 
441 /*
442  * lead_with_offset
443  * returns the value of VE evaluated on a row that is OFFSET
444  * number of rows after the current row within a partition,
445  * per spec.
446  */
447 Datum
449 {
450  return leadlag_common(fcinfo, true, true, false);
451 }
452 
453 /*
454  * lead_with_offset_and_default
455  * same as lead_with_offset but accepts default value
456  * as its third argument.
457  */
458 Datum
460 {
461  return leadlag_common(fcinfo, true, true, true);
462 }
463 
464 /*
465  * first_value
466  * return the value of VE evaluated on the first row of the
467  * window frame, per spec.
468  */
469 Datum
471 {
472  WindowObject winobj = PG_WINDOW_OBJECT();
473  Datum result;
474  bool isnull;
475 
476  result = WinGetFuncArgInFrame(winobj, 0,
477  0, WINDOW_SEEK_HEAD, true,
478  &isnull, NULL);
479  if (isnull)
480  PG_RETURN_NULL();
481 
482  PG_RETURN_DATUM(result);
483 }
484 
485 /*
486  * last_value
487  * return the value of VE evaluated on the last row of the
488  * window frame, per spec.
489  */
490 Datum
492 {
493  WindowObject winobj = PG_WINDOW_OBJECT();
494  Datum result;
495  bool isnull;
496 
497  result = WinGetFuncArgInFrame(winobj, 0,
498  0, WINDOW_SEEK_TAIL, true,
499  &isnull, NULL);
500  if (isnull)
501  PG_RETURN_NULL();
502 
503  PG_RETURN_DATUM(result);
504 }
505 
506 /*
507  * nth_value
508  * return the value of VE evaluated on the n-th row from the first
509  * row of the window frame, per spec.
510  */
511 Datum
513 {
514  WindowObject winobj = PG_WINDOW_OBJECT();
515  bool const_offset;
516  Datum result;
517  bool isnull;
518  int32 nth;
519 
520  nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
521  if (isnull)
522  PG_RETURN_NULL();
523  const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
524 
525  if (nth <= 0)
526  ereport(ERROR,
527  (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
528  errmsg("argument of nth_value must be greater than zero")));
529 
530  result = WinGetFuncArgInFrame(winobj, 0,
531  nth - 1, WINDOW_SEEK_HEAD, const_offset,
532  &isnull, NULL);
533  if (isnull)
534  PG_RETURN_NULL();
535 
536  PG_RETURN_DATUM(result);
537 }
signed int int32
Definition: c.h:429
double float8
Definition: c.h:565
int errcode(int sqlerrcode)
Definition: elog.c:693
int errmsg(const char *fmt,...)
Definition: elog.c:904
#define ERROR
Definition: elog.h:33
#define ereport(elevel,...)
Definition: elog.h:143
bool get_fn_expr_arg_stable(FmgrInfo *flinfo, int argnum)
Definition: fmgr.c:1851
#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(fmt[strlen(fmt) - 1] !='\n')
Datum WinGetFuncArgInPartition(WindowObject winobj, int argno, int relpos, int seektype, bool set_mark, bool *isnull, bool *isout)
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 * WinGetPartitionLocalMemory(WindowObject winobj, Size sz)
Datum WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
int64 WinGetPartitionRowCount(WindowObject winobj)
#define IsA(nodeptr, _type_)
Definition: nodes.h:624
@ MONOTONICFUNC_INCREASING
Definition: plannodes.h:1344
uintptr_t Datum
Definition: postgres.h:411
#define DatumGetInt32(X)
Definition: postgres.h:516
FmgrInfo * flinfo
Definition: fmgr.h:87
Definition: nodes.h:574
MonotonicFunction monotonic
Definition: supportnodes.h:299
int64 rows_per_bucket
Definition: windowfuncs.c:34
int64 remainder
Definition: windowfuncs.c:36
int64 boundary
Definition: windowfuncs.c:35
#define WINDOW_SEEK_TAIL
Definition: windowapi.h:34
#define WINDOW_SEEK_HEAD
Definition: windowapi.h:33
#define PG_WINDOW_OBJECT()
Definition: windowapi.h:39
#define WINDOW_SEEK_CURRENT
Definition: windowapi.h:32
Datum window_lag_with_offset(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:413
static bool rank_up(WindowObject winobj)
Definition: windowfuncs.c:48
Datum window_dense_rank(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:160
Datum window_last_value(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:491
Datum window_percent_rank(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:203
static Datum leadlag_common(FunctionCallInfo fcinfo, bool forward, bool withoffset, bool withdefault)
Definition: windowfuncs.c:349
Datum window_ntile(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:274
Datum window_row_number(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:83
Datum window_first_value(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:470
Datum window_lead(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:436
Datum window_lead_with_offset(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:448
Datum window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:424
Datum window_lag(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:401
Datum window_row_number_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:97
Datum window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:459
Datum window_cume_dist(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:232
Datum window_rank(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:119
struct rank_context rank_context
Datum window_dense_rank_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:180
Datum window_nth_value(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:512
Datum window_rank_support(PG_FUNCTION_ARGS)
Definition: windowfuncs.c:139