Line data Source code
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 : */
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 165878 : rank_up(WindowObject winobj)
50 : {
51 165878 : bool up = false; /* should rank increase? */
52 165878 : int64 curpos = WinGetCurrentPosition(winobj);
53 : rank_context *context;
54 :
55 : context = (rank_context *)
56 165878 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
57 :
58 165878 : if (context->rank == 0)
59 : {
60 : /* first call: rank of first row is always 1 */
61 : Assert(curpos == 0);
62 404 : context->rank = 1;
63 : }
64 : else
65 : {
66 : Assert(curpos > 0);
67 : /* do current and prior tuples match by ORDER BY clause? */
68 165474 : if (!WinRowsArePeers(winobj, curpos - 1, curpos))
69 134238 : up = true;
70 : }
71 :
72 : /* We can advance the mark, but only *after* access to prior row */
73 165878 : WinSetMarkPosition(winobj, curpos);
74 :
75 165878 : return up;
76 : }
77 :
78 :
79 : /*
80 : * row_number
81 : * just increment up from 1 until current partition finishes.
82 : */
83 : Datum
84 457802 : window_row_number(PG_FUNCTION_ARGS)
85 : {
86 457802 : WindowObject winobj = PG_WINDOW_OBJECT();
87 457802 : int64 curpos = WinGetCurrentPosition(winobj);
88 :
89 457802 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
90 457790 : WinSetMarkPosition(winobj, curpos);
91 457790 : PG_RETURN_INT64(curpos + 1);
92 : }
93 :
94 : /*
95 : * window_row_number_support
96 : * prosupport function for window_row_number()
97 : */
98 : Datum
99 764 : window_row_number_support(PG_FUNCTION_ARGS)
100 : {
101 764 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
102 :
103 764 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
104 : {
105 60 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
106 :
107 : /* row_number() is monotonically increasing */
108 60 : req->monotonic = MONOTONICFUNC_INCREASING;
109 60 : PG_RETURN_POINTER(req);
110 : }
111 :
112 704 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
113 : {
114 334 : 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 334 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
123 : FRAMEOPTION_ROWS |
124 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
125 : FRAMEOPTION_END_CURRENT_ROW);
126 :
127 334 : PG_RETURN_POINTER(req);
128 : }
129 :
130 370 : 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 165518 : window_rank(PG_FUNCTION_ARGS)
140 : {
141 165518 : WindowObject winobj = PG_WINDOW_OBJECT();
142 : rank_context *context;
143 : bool up;
144 :
145 165518 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
146 165506 : up = rank_up(winobj);
147 : context = (rank_context *)
148 165506 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
149 165506 : if (up)
150 134160 : context->rank = WinGetCurrentPosition(winobj) + 1;
151 :
152 165506 : PG_RETURN_INT64(context->rank);
153 : }
154 :
155 : /*
156 : * window_rank_support
157 : * prosupport function for window_rank()
158 : */
159 : Datum
160 378 : window_rank_support(PG_FUNCTION_ARGS)
161 : {
162 378 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
163 :
164 378 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
165 : {
166 12 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
167 :
168 : /* rank() is monotonically increasing */
169 12 : req->monotonic = MONOTONICFUNC_INCREASING;
170 12 : PG_RETURN_POINTER(req);
171 : }
172 :
173 366 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
174 : {
175 164 : 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 164 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
187 : FRAMEOPTION_ROWS |
188 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
189 : FRAMEOPTION_END_CURRENT_ROW);
190 :
191 164 : PG_RETURN_POINTER(req);
192 : }
193 :
194 202 : PG_RETURN_POINTER(NULL);
195 : }
196 :
197 : /*
198 : * dense_rank
199 : * Rank increases by 1 when key columns change.
200 : */
201 : Datum
202 144 : window_dense_rank(PG_FUNCTION_ARGS)
203 : {
204 144 : WindowObject winobj = PG_WINDOW_OBJECT();
205 : rank_context *context;
206 : bool up;
207 :
208 144 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
209 132 : up = rank_up(winobj);
210 : context = (rank_context *)
211 132 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
212 132 : if (up)
213 30 : context->rank++;
214 :
215 132 : PG_RETURN_INT64(context->rank);
216 : }
217 :
218 : /*
219 : * window_dense_rank_support
220 : * prosupport function for window_dense_rank()
221 : */
222 : Datum
223 108 : window_dense_rank_support(PG_FUNCTION_ARGS)
224 : {
225 108 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
226 :
227 108 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
228 : {
229 18 : SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
230 :
231 : /* dense_rank() is monotonically increasing */
232 18 : req->monotonic = MONOTONICFUNC_INCREASING;
233 18 : PG_RETURN_POINTER(req);
234 : }
235 :
236 90 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
237 : {
238 42 : 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 42 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
247 : FRAMEOPTION_ROWS |
248 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
249 : FRAMEOPTION_END_CURRENT_ROW);
250 :
251 42 : PG_RETURN_POINTER(req);
252 : }
253 :
254 48 : 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 132 : window_percent_rank(PG_FUNCTION_ARGS)
265 : {
266 132 : WindowObject winobj = PG_WINDOW_OBJECT();
267 : rank_context *context;
268 : bool up;
269 132 : int64 totalrows = WinGetPartitionRowCount(winobj);
270 :
271 : Assert(totalrows > 0);
272 132 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
273 :
274 120 : up = rank_up(winobj);
275 : context = (rank_context *)
276 120 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
277 120 : if (up)
278 24 : context->rank = WinGetCurrentPosition(winobj) + 1;
279 :
280 : /* return zero if there's only one row, per spec */
281 120 : if (totalrows <= 1)
282 6 : PG_RETURN_FLOAT8(0.0);
283 :
284 114 : 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 60 : window_percent_rank_support(PG_FUNCTION_ARGS)
293 : {
294 60 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
295 :
296 60 : 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 60 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
306 : {
307 30 : 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 30 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
316 : FRAMEOPTION_ROWS |
317 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
318 : FRAMEOPTION_END_CURRENT_ROW);
319 :
320 30 : PG_RETURN_POINTER(req);
321 : }
322 :
323 30 : 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 132 : window_cume_dist(PG_FUNCTION_ARGS)
335 : {
336 132 : WindowObject winobj = PG_WINDOW_OBJECT();
337 : rank_context *context;
338 : bool up;
339 132 : int64 totalrows = WinGetPartitionRowCount(winobj);
340 :
341 : Assert(totalrows > 0);
342 132 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
343 :
344 120 : up = rank_up(winobj);
345 : context = (rank_context *)
346 120 : WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
347 120 : 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 54 : context->rank = WinGetCurrentPosition(winobj) + 1;
356 :
357 : /*
358 : * start from current + 1
359 : */
360 120 : for (row = context->rank; row < totalrows; row++)
361 : {
362 90 : if (!WinRowsArePeers(winobj, row - 1, row))
363 24 : break;
364 66 : context->rank++;
365 : }
366 : }
367 :
368 120 : 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 60 : window_cume_dist_support(PG_FUNCTION_ARGS)
377 : {
378 60 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
379 :
380 60 : 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 60 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
390 : {
391 30 : 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 30 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
400 : FRAMEOPTION_ROWS |
401 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
402 : FRAMEOPTION_END_CURRENT_ROW);
403 :
404 30 : PG_RETURN_POINTER(req);
405 : }
406 :
407 30 : 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 186 : window_ntile(PG_FUNCTION_ARGS)
417 : {
418 186 : WindowObject winobj = PG_WINDOW_OBJECT();
419 : ntile_context *context;
420 :
421 186 : WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
422 : context = (ntile_context *)
423 174 : WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
424 :
425 174 : if (context->ntile == 0)
426 : {
427 : /* first call */
428 : int64 total;
429 : int32 nbuckets;
430 : bool isnull;
431 :
432 48 : total = WinGetPartitionRowCount(winobj);
433 48 : 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 48 : if (isnull)
440 12 : 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 36 : if (nbuckets <= 0)
447 6 : ereport(ERROR,
448 : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
449 : errmsg("argument of ntile must be greater than zero")));
450 :
451 30 : context->ntile = 1;
452 30 : context->rows_per_bucket = 0;
453 30 : context->boundary = total / nbuckets;
454 30 : 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 30 : context->remainder = total % nbuckets;
463 30 : if (context->remainder != 0)
464 18 : context->boundary++;
465 : }
466 : }
467 :
468 156 : context->rows_per_bucket++;
469 156 : if (context->boundary < context->rows_per_bucket)
470 : {
471 : /* ntile up */
472 18 : if (context->remainder != 0 && context->ntile == context->remainder)
473 : {
474 6 : context->remainder = 0;
475 6 : context->boundary -= 1;
476 : }
477 18 : context->ntile += 1;
478 18 : context->rows_per_bucket = 1;
479 : }
480 :
481 156 : PG_RETURN_INT32(context->ntile);
482 : }
483 :
484 : /*
485 : * window_ntile_support
486 : * prosupport function for window_ntile()
487 : */
488 : Datum
489 144 : window_ntile_support(PG_FUNCTION_ARGS)
490 : {
491 144 : Node *rawreq = (Node *) PG_GETARG_POINTER(0);
492 :
493 144 : if (IsA(rawreq, SupportRequestWFuncMonotonic))
494 : {
495 24 : 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 24 : req->monotonic = MONOTONICFUNC_INCREASING;
502 24 : PG_RETURN_POINTER(req);
503 : }
504 :
505 120 : if (IsA(rawreq, SupportRequestOptimizeWindowClause))
506 : {
507 54 : 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 54 : req->frameOptions = (FRAMEOPTION_NONDEFAULT |
516 : FRAMEOPTION_ROWS |
517 : FRAMEOPTION_START_UNBOUNDED_PRECEDING |
518 : FRAMEOPTION_END_CURRENT_ROW);
519 :
520 54 : PG_RETURN_POINTER(req);
521 : }
522 :
523 66 : 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 237060 : leadlag_common(FunctionCallInfo fcinfo,
535 : bool forward, bool withoffset, bool withdefault)
536 : {
537 237060 : WindowObject winobj = PG_WINDOW_OBJECT();
538 : int32 offset;
539 : bool const_offset;
540 : Datum result;
541 : bool isnull;
542 : bool isout;
543 :
544 237060 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
545 237060 : if (withoffset)
546 : {
547 900 : offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
548 900 : if (isnull)
549 0 : PG_RETURN_NULL();
550 900 : const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
551 : }
552 : else
553 : {
554 236160 : offset = 1;
555 236160 : const_offset = true;
556 : }
557 :
558 237060 : result = WinGetFuncArgInPartition(winobj, 0,
559 : (forward ? offset : -offset),
560 : WINDOW_SEEK_CURRENT,
561 : const_offset,
562 : &isnull, &isout);
563 :
564 237060 : 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 522 : if (withdefault)
571 96 : result = WinGetFuncArgCurrent(winobj, 2, &isnull);
572 : }
573 :
574 237060 : if (isnull)
575 498 : PG_RETURN_NULL();
576 :
577 236562 : 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 235632 : window_lag(PG_FUNCTION_ARGS)
588 : {
589 235632 : 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 300 : window_lag_with_offset(PG_FUNCTION_ARGS)
600 : {
601 300 : 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 120 : window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
611 : {
612 120 : 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 528 : window_lead(PG_FUNCTION_ARGS)
623 : {
624 528 : 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 360 : window_lead_with_offset(PG_FUNCTION_ARGS)
635 : {
636 360 : 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 120 : window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
646 : {
647 120 : 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 4146 : window_first_value(PG_FUNCTION_ARGS)
657 : {
658 4146 : WindowObject winobj = PG_WINDOW_OBJECT();
659 : Datum result;
660 : bool isnull;
661 :
662 4146 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
663 4146 : result = WinGetFuncArgInFrame(winobj, 0,
664 : 0, WINDOW_SEEK_HEAD, true,
665 : &isnull, NULL);
666 4074 : if (isnull)
667 456 : PG_RETURN_NULL();
668 :
669 3618 : 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 4842 : window_last_value(PG_FUNCTION_ARGS)
679 : {
680 4842 : WindowObject winobj = PG_WINDOW_OBJECT();
681 : Datum result;
682 : bool isnull;
683 :
684 4842 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
685 4842 : result = WinGetFuncArgInFrame(winobj, 0,
686 : 0, WINDOW_SEEK_TAIL, true,
687 : &isnull, NULL);
688 4836 : if (isnull)
689 450 : PG_RETURN_NULL();
690 :
691 4386 : 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 966 : window_nth_value(PG_FUNCTION_ARGS)
701 : {
702 966 : WindowObject winobj = PG_WINDOW_OBJECT();
703 : bool const_offset;
704 : Datum result;
705 : bool isnull;
706 : int32 nth;
707 :
708 966 : WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
709 966 : nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
710 966 : if (isnull)
711 0 : PG_RETURN_NULL();
712 966 : const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
713 :
714 966 : if (nth <= 0)
715 6 : ereport(ERROR,
716 : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
717 : errmsg("argument of nth_value must be greater than zero")));
718 :
719 960 : result = WinGetFuncArgInFrame(winobj, 0,
720 : nth - 1, WINDOW_SEEK_HEAD, const_offset,
721 : &isnull, NULL);
722 960 : if (isnull)
723 138 : PG_RETURN_NULL();
724 :
725 822 : PG_RETURN_DATUM(result);
726 : }
|