Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * windowfuncs.c
4 : * Standard window functions defined in SQL spec.
5 : *
6 : * Portions Copyright (c) 2000-2026, 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 : */
24 : typedef struct rank_context
25 : {
26 : int64 rank; /* current rank */
27 : } rank_context;
28 :
29 : /*
30 : * ntile process information
31 : */
32 : typedef 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) */
38 : } ntile_context;
39 :
40 : static bool rank_up(WindowObject winobj);
41 : static Datum leadlag_common(FunctionCallInfo fcinfo,
42 : bool forward, bool withoffset, bool withdefault);
43 :
44 :
45 : /*
46 : * utility routine for *_rank functions.
47 : */
48 : static bool
49 82939 : rank_up(WindowObject winobj)
50 : {
51 82939 : bool up = false; /* should rank increase? */
52 82939 : int64 curpos = WinGetCurrentPosition(winobj);
53 : rank_context *context;
54 :
55 : context = (rank_context *)
56 82939 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
57 :
58 82939 : if (context->rank == 0)
59 : {
60 : /* first call: rank of first row is always 1 */
61 : Assert(curpos == 0);
62 202 : context->rank = 1;
63 : }
64 : else
65 : {
66 : Assert(curpos > 0);
67 : /* do current and prior tuples match by ORDER BY clause? */
68 82737 : if (!WinRowsArePeers(winobj, curpos - 1, curpos))
69 64323 : up = true;
70 : }
71 :
72 : /* We can advance the mark, but only *after* access to prior row */
73 82939 : WinSetMarkPosition(winobj, curpos);
74 :
75 82939 : return up;
76 : }
77 :
78 :
79 : /*
80 : * row_number
81 : * just increment up from 1 until current partition finishes.
82 : */
83 : Datum
84 228901 : window_row_number(PG_FUNCTION_ARGS)
85 : {
86 228901 : WindowObject winobj = PG_WINDOW_OBJECT();
87 228901 : int64 curpos = WinGetCurrentPosition(winobj);
88 :
89 228901 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
90 228895 : WinSetMarkPosition(winobj, curpos);
91 228895 : PG_RETURN_INT64(curpos + 1);
92 : }
93 :
94 : /*
95 : * window_row_number_support
96 : * prosupport function for window_row_number()
97 : */
98 : Datum
99 382 : window_row_number_support(PG_FUNCTION_ARGS)
100 : {
101 382 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
102 :
103 382 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
104 : {
105 30 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
106 :
107 : /* row_number() is monotonically increasing */
108 30 : req->monotonic = MONOTONICFUNC_INCREASING;
109 30 : PG_RETURN_POINTER(req);
110 : }
111 :
112 352 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
113 : {
114 167 : SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
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 : */
122 167 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
123 : FRAMEOPTION_ROWS |
124 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
125 : FRAMEOPTION_END_CURRENT_ROW);
126 :
127 167 : PG_RETURN_POINTER(req);
128 : }
129 :
130 185 : 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 : */
138 : Datum
139 82759 : window_rank(PG_FUNCTION_ARGS)
140 : {
141 82759 : WindowObject winobj = PG_WINDOW_OBJECT();
142 : rank_context *context;
143 : bool up;
144 :
145 82759 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
146 82753 : up = rank_up(winobj);
147 : context = (rank_context *)
148 82753 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
149 82753 : if (up)
150 64284 : context->rank = WinGetCurrentPosition(winobj) + 1;
151 :
152 82753 : PG_RETURN_INT64(context->rank);
153 : }
154 :
155 : /*
156 : * window_rank_support
157 : * prosupport function for window_rank()
158 : */
159 : Datum
160 189 : window_rank_support(PG_FUNCTION_ARGS)
161 : {
162 189 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
163 :
164 189 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
165 : {
166 6 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
167 :
168 : /* rank() is monotonically increasing */
169 6 : req->monotonic = MONOTONICFUNC_INCREASING;
170 6 : PG_RETURN_POINTER(req);
171 : }
172 :
173 183 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
174 : {
175 82 : SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
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 : */
186 82 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
187 : FRAMEOPTION_ROWS |
188 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
189 : FRAMEOPTION_END_CURRENT_ROW);
190 :
191 82 : PG_RETURN_POINTER(req);
192 : }
193 :
194 101 : PG_RETURN_POINTER(NULL);
195 : }
196 :
197 : /*
198 : * dense_rank
199 : * Rank increases by 1 when key columns change.
200 : */
201 : Datum
202 72 : window_dense_rank(PG_FUNCTION_ARGS)
203 : {
204 72 : WindowObject winobj = PG_WINDOW_OBJECT();
205 : rank_context *context;
206 : bool up;
207 :
208 72 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
209 66 : up = rank_up(winobj);
210 : context = (rank_context *)
211 66 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
212 66 : if (up)
213 15 : context->rank++;
214 :
215 66 : PG_RETURN_INT64(context->rank);
216 : }
217 :
218 : /*
219 : * window_dense_rank_support
220 : * prosupport function for window_dense_rank()
221 : */
222 : Datum
223 54 : window_dense_rank_support(PG_FUNCTION_ARGS)
224 : {
225 54 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
226 :
227 54 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
228 : {
229 9 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
230 :
231 : /* dense_rank() is monotonically increasing */
232 9 : req->monotonic = MONOTONICFUNC_INCREASING;
233 9 : PG_RETURN_POINTER(req);
234 : }
235 :
236 45 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
237 : {
238 21 : SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
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 : */
246 21 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
247 : FRAMEOPTION_ROWS |
248 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
249 : FRAMEOPTION_END_CURRENT_ROW);
250 :
251 21 : PG_RETURN_POINTER(req);
252 : }
253 :
254 24 : 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 : */
263 : Datum
264 66 : window_percent_rank(PG_FUNCTION_ARGS)
265 : {
266 66 : WindowObject winobj = PG_WINDOW_OBJECT();
267 : rank_context *context;
268 : bool up;
269 66 : int64 totalrows = WinGetPartitionRowCount(winobj);
270 :
271 : Assert(totalrows > 0);
272 66 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
273 :
274 60 : up = rank_up(winobj);
275 : context = (rank_context *)
276 60 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
277 60 : if (up)
278 12 : context->rank = WinGetCurrentPosition(winobj) + 1;
279 :
280 : /* return zero if there's only one row, per spec */
281 60 : if (totalrows <= 1)
282 3 : PG_RETURN_FLOAT8(0.0);
283 :
284 57 : 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 : */
291 : Datum
292 30 : window_percent_rank_support(PG_FUNCTION_ARGS)
293 : {
294 30 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
295 :
296 30 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
297 : {
298 0 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
299 :
300 : /* percent_rank() is monotonically increasing */
301 0 : req->monotonic = MONOTONICFUNC_INCREASING;
302 0 : PG_RETURN_POINTER(req);
303 : }
304 :
305 30 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
306 : {
307 15 : SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
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 : */
315 15 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
316 : FRAMEOPTION_ROWS |
317 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
318 : FRAMEOPTION_END_CURRENT_ROW);
319 :
320 15 : PG_RETURN_POINTER(req);
321 : }
322 :
323 15 : 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 : */
333 : Datum
334 66 : window_cume_dist(PG_FUNCTION_ARGS)
335 : {
336 66 : WindowObject winobj = PG_WINDOW_OBJECT();
337 : rank_context *context;
338 : bool up;
339 66 : int64 totalrows = WinGetPartitionRowCount(winobj);
340 :
341 : Assert(totalrows > 0);
342 66 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
343 :
344 60 : up = rank_up(winobj);
345 : context = (rank_context *)
346 60 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
347 60 : 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 27 : context->rank = WinGetCurrentPosition(winobj) + 1;
356 :
357 : /*
358 : * start from current + 1
359 : */
360 60 : for (row = context->rank; row < totalrows; row++)
361 : {
362 45 : if (!WinRowsArePeers(winobj, row - 1, row))
363 12 : break;
364 33 : context->rank++;
365 : }
366 : }
367 :
368 60 : PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
369 : }
370 :
371 : /*
372 : * window_cume_dist_support
373 : * prosupport function for window_cume_dist()
374 : */
375 : Datum
376 30 : window_cume_dist_support(PG_FUNCTION_ARGS)
377 : {
378 30 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
379 :
380 30 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
381 : {
382 0 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
383 :
384 : /* cume_dist() is monotonically increasing */
385 0 : req->monotonic = MONOTONICFUNC_INCREASING;
386 0 : PG_RETURN_POINTER(req);
387 : }
388 :
389 30 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
390 : {
391 15 : SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
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 : */
399 15 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
400 : FRAMEOPTION_ROWS |
401 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
402 : FRAMEOPTION_END_CURRENT_ROW);
403 :
404 15 : PG_RETURN_POINTER(req);
405 : }
406 :
407 15 : 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 : */
415 : Datum
416 93 : window_ntile(PG_FUNCTION_ARGS)
417 : {
418 93 : WindowObject winobj = PG_WINDOW_OBJECT();
419 : ntile_context *context;
420 :
421 93 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
422 : context = (ntile_context *)
423 87 : WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
424 :
425 87 : if (context->ntile == 0)
426 : {
427 : /* first call */
428 : int64 total;
429 : int32 nbuckets;
430 : bool isnull;
431 :
432 24 : total = WinGetPartitionRowCount(winobj);
433 24 : 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 24 : if (isnull)
440 6 : PG_RETURN_NULL();
441 :
442 : /*
443 : * per spec: If NT is less than or equal to 0 (zero), then an
444 : * exception condition is raised.
445 : */
446 18 : if (nbuckets <= 0)
447 3 : ereport(ERROR,
448 : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
449 : errmsg("argument of ntile must be greater than zero")));
450 :
451 15 : context->ntile = 1;
452 15 : context->rows_per_bucket = 0;
453 15 : context->boundary = total / nbuckets;
454 15 : if (context->boundary <= 0)
455 0 : context->boundary = 1;
456 : else
457 : {
458 : /*
459 : * If the total number is not divisible, add 1 row to leading
460 : * buckets.
461 : */
462 15 : context->remainder = total % nbuckets;
463 15 : if (context->remainder != 0)
464 9 : context->boundary++;
465 : }
466 : }
467 :
468 78 : context->rows_per_bucket++;
469 78 : if (context->boundary < context->rows_per_bucket)
470 : {
471 : /* ntile up */
472 9 : if (context->remainder != 0 && context->ntile == context->remainder)
473 : {
474 3 : context->remainder = 0;
475 3 : context->boundary -= 1;
476 : }
477 9 : context->ntile += 1;
478 9 : context->rows_per_bucket = 1;
479 : }
480 :
481 78 : PG_RETURN_INT32(context->ntile);
482 : }
483 :
484 : /*
485 : * window_ntile_support
486 : * prosupport function for window_ntile()
487 : */
488 : Datum
489 72 : window_ntile_support(PG_FUNCTION_ARGS)
490 : {
491 72 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
492 :
493 72 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
494 : {
495 12 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
496 :
497 : /*
498 : * ntile() is monotonically increasing as the number of buckets cannot
499 : * change after the first call
500 : */
501 12 : req->monotonic = MONOTONICFUNC_INCREASING;
502 12 : PG_RETURN_POINTER(req);
503 : }
504 :
505 60 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
506 : {
507 27 : SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
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 : */
515 27 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
516 : FRAMEOPTION_ROWS |
517 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
518 : FRAMEOPTION_END_CURRENT_ROW);
519 :
520 27 : PG_RETURN_POINTER(req);
521 : }
522 :
523 33 : 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 : */
533 : static Datum
534 118530 : leadlag_common(FunctionCallInfo fcinfo,
535 : bool forward, bool withoffset, bool withdefault)
536 : {
537 118530 : WindowObject winobj = PG_WINDOW_OBJECT();
538 : int32 offset;
539 : bool const_offset;
540 : Datum result;
541 : bool isnull;
542 : bool isout;
543 :
544 118530 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
545 118530 : if (withoffset)
546 : {
547 450 : offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
548 450 : if (isnull)
549 0 : PG_RETURN_NULL();
550 450 : const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
551 : }
552 : else
553 : {
554 118080 : offset = 1;
555 118080 : const_offset = true;
556 : }
557 :
558 118530 : result = WinGetFuncArgInPartition(winobj, 0,
559 : (forward ? offset : -offset),
560 : WINDOW_SEEK_CURRENT,
561 : const_offset,
562 : &isnull, &isout);
563 :
564 118530 : 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 261 : if (withdefault)
571 48 : result = WinGetFuncArgCurrent(winobj, 2, &isnull);
572 : }
573 :
574 118530 : if (isnull)
575 249 : PG_RETURN_NULL();
576 :
577 118281 : 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 : */
586 : Datum
587 117816 : window_lag(PG_FUNCTION_ARGS)
588 : {
589 117816 : 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 : */
598 : Datum
599 150 : window_lag_with_offset(PG_FUNCTION_ARGS)
600 : {
601 150 : 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 : */
609 : Datum
610 60 : window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
611 : {
612 60 : 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 : */
621 : Datum
622 264 : window_lead(PG_FUNCTION_ARGS)
623 : {
624 264 : 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 : */
633 : Datum
634 180 : window_lead_with_offset(PG_FUNCTION_ARGS)
635 : {
636 180 : 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 : */
644 : Datum
645 60 : window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
646 : {
647 60 : 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 : */
655 : Datum
656 2073 : window_first_value(PG_FUNCTION_ARGS)
657 : {
658 2073 : WindowObject winobj = PG_WINDOW_OBJECT();
659 : Datum result;
660 : bool isnull;
661 :
662 2073 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
663 2073 : result = WinGetFuncArgInFrame(winobj, 0,
664 : 0, WINDOW_SEEK_HEAD, true,
665 : &isnull, NULL);
666 2037 : if (isnull)
667 228 : PG_RETURN_NULL();
668 :
669 1809 : 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 : */
677 : Datum
678 2421 : window_last_value(PG_FUNCTION_ARGS)
679 : {
680 2421 : WindowObject winobj = PG_WINDOW_OBJECT();
681 : Datum result;
682 : bool isnull;
683 :
684 2421 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
685 2421 : result = WinGetFuncArgInFrame(winobj, 0,
686 : 0, WINDOW_SEEK_TAIL, true,
687 : &isnull, NULL);
688 2418 : if (isnull)
689 225 : PG_RETURN_NULL();
690 :
691 2193 : 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 : */
699 : Datum
700 483 : window_nth_value(PG_FUNCTION_ARGS)
701 : {
702 483 : WindowObject winobj = PG_WINDOW_OBJECT();
703 : bool const_offset;
704 : Datum result;
705 : bool isnull;
706 : int32 nth;
707 :
708 483 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
709 483 : nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
710 483 : if (isnull)
711 0 : PG_RETURN_NULL();
712 483 : const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
713 :
714 483 : if (nth <= 0)
715 3 : ereport(ERROR,
716 : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
717 : errmsg("argument of nth_value must be greater than zero")));
718 :
719 480 : result = WinGetFuncArgInFrame(winobj, 0,
720 : nth - 1, WINDOW_SEEK_HEAD, const_offset,
721 : &isnull, NULL);
722 480 : if (isnull)
723 69 : PG_RETURN_NULL();
724 :
725 411 : PG_RETURN_DATUM(result);
726 : }
|