Header And Logo

PostgreSQL
| The world's most advanced open source database.

windowfuncs.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * windowfuncs.c
00004  *    Standard window functions defined in SQL spec.
00005  *
00006  * Portions Copyright (c) 2000-2013, PostgreSQL Global Development Group
00007  *
00008  *
00009  * IDENTIFICATION
00010  *    src/backend/utils/adt/windowfuncs.c
00011  *
00012  *-------------------------------------------------------------------------
00013  */
00014 #include "postgres.h"
00015 
00016 #include "utils/builtins.h"
00017 #include "windowapi.h"
00018 
00019 /*
00020  * ranking process information
00021  */
00022 typedef struct rank_context
00023 {
00024     int64       rank;           /* current rank */
00025 } rank_context;
00026 
00027 /*
00028  * ntile process information
00029  */
00030 typedef struct
00031 {
00032     int32       ntile;          /* current result */
00033     int64       rows_per_bucket;    /* row number of current bucket */
00034     int64       boundary;       /* how many rows should be in the bucket */
00035     int64       remainder;      /* (total rows) % (bucket num) */
00036 } ntile_context;
00037 
00038 static bool rank_up(WindowObject winobj);
00039 static Datum leadlag_common(FunctionCallInfo fcinfo,
00040                bool forward, bool withoffset, bool withdefault);
00041 
00042 
00043 /*
00044  * utility routine for *_rank functions.
00045  */
00046 static bool
00047 rank_up(WindowObject winobj)
00048 {
00049     bool        up = false;     /* should rank increase? */
00050     int64       curpos = WinGetCurrentPosition(winobj);
00051     rank_context *context;
00052 
00053     context = (rank_context *)
00054         WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
00055 
00056     if (context->rank == 0)
00057     {
00058         /* first call: rank of first row is always 1 */
00059         Assert(curpos == 0);
00060         context->rank = 1;
00061     }
00062     else
00063     {
00064         Assert(curpos > 0);
00065         /* do current and prior tuples match by ORDER BY clause? */
00066         if (!WinRowsArePeers(winobj, curpos - 1, curpos))
00067             up = true;
00068     }
00069 
00070     /* We can advance the mark, but only *after* acccess to prior row */
00071     WinSetMarkPosition(winobj, curpos);
00072 
00073     return up;
00074 }
00075 
00076 
00077 /*
00078  * row_number
00079  * just increment up from 1 until current partition finishes.
00080  */
00081 Datum
00082 window_row_number(PG_FUNCTION_ARGS)
00083 {
00084     WindowObject winobj = PG_WINDOW_OBJECT();
00085     int64       curpos = WinGetCurrentPosition(winobj);
00086 
00087     WinSetMarkPosition(winobj, curpos);
00088     PG_RETURN_INT64(curpos + 1);
00089 }
00090 
00091 
00092 /*
00093  * rank
00094  * Rank changes when key columns change.
00095  * The new rank number is the current row number.
00096  */
00097 Datum
00098 window_rank(PG_FUNCTION_ARGS)
00099 {
00100     WindowObject winobj = PG_WINDOW_OBJECT();
00101     rank_context *context;
00102     bool        up;
00103 
00104     up = rank_up(winobj);
00105     context = (rank_context *)
00106         WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
00107     if (up)
00108         context->rank = WinGetCurrentPosition(winobj) + 1;
00109 
00110     PG_RETURN_INT64(context->rank);
00111 }
00112 
00113 /*
00114  * dense_rank
00115  * Rank increases by 1 when key columns change.
00116  */
00117 Datum
00118 window_dense_rank(PG_FUNCTION_ARGS)
00119 {
00120     WindowObject winobj = PG_WINDOW_OBJECT();
00121     rank_context *context;
00122     bool        up;
00123 
00124     up = rank_up(winobj);
00125     context = (rank_context *)
00126         WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
00127     if (up)
00128         context->rank++;
00129 
00130     PG_RETURN_INT64(context->rank);
00131 }
00132 
00133 /*
00134  * percent_rank
00135  * return fraction between 0 and 1 inclusive,
00136  * which is described as (RK - 1) / (NR - 1), where RK is the current row's
00137  * rank and NR is the total number of rows, per spec.
00138  */
00139 Datum
00140 window_percent_rank(PG_FUNCTION_ARGS)
00141 {
00142     WindowObject winobj = PG_WINDOW_OBJECT();
00143     rank_context *context;
00144     bool        up;
00145     int64       totalrows = WinGetPartitionRowCount(winobj);
00146 
00147     Assert(totalrows > 0);
00148 
00149     up = rank_up(winobj);
00150     context = (rank_context *)
00151         WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
00152     if (up)
00153         context->rank = WinGetCurrentPosition(winobj) + 1;
00154 
00155     /* return zero if there's only one row, per spec */
00156     if (totalrows <= 1)
00157         PG_RETURN_FLOAT8(0.0);
00158 
00159     PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1));
00160 }
00161 
00162 /*
00163  * cume_dist
00164  * return fraction betweeen 0 and 1 inclusive,
00165  * which is described as NP / NR, where NP is the number of rows preceding or
00166  * peers to the current row, and NR is the total number of rows, per spec.
00167  */
00168 Datum
00169 window_cume_dist(PG_FUNCTION_ARGS)
00170 {
00171     WindowObject winobj = PG_WINDOW_OBJECT();
00172     rank_context *context;
00173     bool        up;
00174     int64       totalrows = WinGetPartitionRowCount(winobj);
00175 
00176     Assert(totalrows > 0);
00177 
00178     up = rank_up(winobj);
00179     context = (rank_context *)
00180         WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
00181     if (up || context->rank == 1)
00182     {
00183         /*
00184          * The current row is not peer to prior row or is just the first, so
00185          * count up the number of rows that are peer to the current.
00186          */
00187         int64       row;
00188 
00189         context->rank = WinGetCurrentPosition(winobj) + 1;
00190 
00191         /*
00192          * start from current + 1
00193          */
00194         for (row = context->rank; row < totalrows; row++)
00195         {
00196             if (!WinRowsArePeers(winobj, row - 1, row))
00197                 break;
00198             context->rank++;
00199         }
00200     }
00201 
00202     PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
00203 }
00204 
00205 /*
00206  * ntile
00207  * compute an exact numeric value with scale 0 (zero),
00208  * ranging from 1 (one) to n, per spec.
00209  */
00210 Datum
00211 window_ntile(PG_FUNCTION_ARGS)
00212 {
00213     WindowObject winobj = PG_WINDOW_OBJECT();
00214     ntile_context *context;
00215 
00216     context = (ntile_context *)
00217         WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
00218 
00219     if (context->ntile == 0)
00220     {
00221         /* first call */
00222         int64       total;
00223         int32       nbuckets;
00224         bool        isnull;
00225 
00226         total = WinGetPartitionRowCount(winobj);
00227         nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull));
00228 
00229         /*
00230          * per spec: If NT is the null value, then the result is the null
00231          * value.
00232          */
00233         if (isnull)
00234             PG_RETURN_NULL();
00235 
00236         /*
00237          * per spec: If NT is less than or equal to 0 (zero), then an
00238          * exception condition is raised.
00239          */
00240         if (nbuckets <= 0)
00241             ereport(ERROR,
00242                     (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
00243                      errmsg("argument of ntile must be greater than zero")));
00244 
00245         context->ntile = 1;
00246         context->rows_per_bucket = 0;
00247         context->boundary = total / nbuckets;
00248         if (context->boundary <= 0)
00249             context->boundary = 1;
00250         else
00251         {
00252             /*
00253              * If the total number is not divisible, add 1 row to leading
00254              * buckets.
00255              */
00256             context->remainder = total % nbuckets;
00257             if (context->remainder != 0)
00258                 context->boundary++;
00259         }
00260     }
00261 
00262     context->rows_per_bucket++;
00263     if (context->boundary < context->rows_per_bucket)
00264     {
00265         /* ntile up */
00266         if (context->remainder != 0 && context->ntile == context->remainder)
00267         {
00268             context->remainder = 0;
00269             context->boundary -= 1;
00270         }
00271         context->ntile += 1;
00272         context->rows_per_bucket = 1;
00273     }
00274 
00275     PG_RETURN_INT32(context->ntile);
00276 }
00277 
00278 /*
00279  * leadlag_common
00280  * common operation of lead() and lag()
00281  * For lead() forward is true, whereas for lag() it is false.
00282  * withoffset indicates we have an offset second argument.
00283  * withdefault indicates we have a default third argument.
00284  */
00285 static Datum
00286 leadlag_common(FunctionCallInfo fcinfo,
00287                bool forward, bool withoffset, bool withdefault)
00288 {
00289     WindowObject winobj = PG_WINDOW_OBJECT();
00290     int32       offset;
00291     bool        const_offset;
00292     Datum       result;
00293     bool        isnull;
00294     bool        isout;
00295 
00296     if (withoffset)
00297     {
00298         offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
00299         if (isnull)
00300             PG_RETURN_NULL();
00301         const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
00302     }
00303     else
00304     {
00305         offset = 1;
00306         const_offset = true;
00307     }
00308 
00309     result = WinGetFuncArgInPartition(winobj, 0,
00310                                       (forward ? offset : -offset),
00311                                       WINDOW_SEEK_CURRENT,
00312                                       const_offset,
00313                                       &isnull, &isout);
00314 
00315     if (isout)
00316     {
00317         /*
00318          * target row is out of the partition; supply default value if
00319          * provided.  otherwise it'll stay NULL
00320          */
00321         if (withdefault)
00322             result = WinGetFuncArgCurrent(winobj, 2, &isnull);
00323     }
00324 
00325     if (isnull)
00326         PG_RETURN_NULL();
00327 
00328     PG_RETURN_DATUM(result);
00329 }
00330 
00331 /*
00332  * lag
00333  * returns the value of VE evaluated on a row that is 1
00334  * row before the current row within a partition,
00335  * per spec.
00336  */
00337 Datum
00338 window_lag(PG_FUNCTION_ARGS)
00339 {
00340     return leadlag_common(fcinfo, false, false, false);
00341 }
00342 
00343 /*
00344  * lag_with_offset
00345  * returns the value of VE evelulated on a row that is OFFSET
00346  * rows before the current row within a partition,
00347  * per spec.
00348  */
00349 Datum
00350 window_lag_with_offset(PG_FUNCTION_ARGS)
00351 {
00352     return leadlag_common(fcinfo, false, true, false);
00353 }
00354 
00355 /*
00356  * lag_with_offset_and_default
00357  * same as lag_with_offset but accepts default value
00358  * as its third argument.
00359  */
00360 Datum
00361 window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
00362 {
00363     return leadlag_common(fcinfo, false, true, true);
00364 }
00365 
00366 /*
00367  * lead
00368  * returns the value of VE evaluated on a row that is 1
00369  * row after the current row within a partition,
00370  * per spec.
00371  */
00372 Datum
00373 window_lead(PG_FUNCTION_ARGS)
00374 {
00375     return leadlag_common(fcinfo, true, false, false);
00376 }
00377 
00378 /*
00379  * lead_with_offset
00380  * returns the value of VE evaluated on a row that is OFFSET
00381  * number of rows after the current row within a partition,
00382  * per spec.
00383  */
00384 Datum
00385 window_lead_with_offset(PG_FUNCTION_ARGS)
00386 {
00387     return leadlag_common(fcinfo, true, true, false);
00388 }
00389 
00390 /*
00391  * lead_with_offset_and_default
00392  * same as lead_with_offset but accepts default value
00393  * as its third argument.
00394  */
00395 Datum
00396 window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
00397 {
00398     return leadlag_common(fcinfo, true, true, true);
00399 }
00400 
00401 /*
00402  * first_value
00403  * return the value of VE evaluated on the first row of the
00404  * window frame, per spec.
00405  */
00406 Datum
00407 window_first_value(PG_FUNCTION_ARGS)
00408 {
00409     WindowObject winobj = PG_WINDOW_OBJECT();
00410     Datum       result;
00411     bool        isnull;
00412 
00413     result = WinGetFuncArgInFrame(winobj, 0,
00414                                   0, WINDOW_SEEK_HEAD, true,
00415                                   &isnull, NULL);
00416     if (isnull)
00417         PG_RETURN_NULL();
00418 
00419     PG_RETURN_DATUM(result);
00420 }
00421 
00422 /*
00423  * last_value
00424  * return the value of VE evaluated on the last row of the
00425  * window frame, per spec.
00426  */
00427 Datum
00428 window_last_value(PG_FUNCTION_ARGS)
00429 {
00430     WindowObject winobj = PG_WINDOW_OBJECT();
00431     Datum       result;
00432     bool        isnull;
00433 
00434     result = WinGetFuncArgInFrame(winobj, 0,
00435                                   0, WINDOW_SEEK_TAIL, true,
00436                                   &isnull, NULL);
00437     if (isnull)
00438         PG_RETURN_NULL();
00439 
00440     PG_RETURN_DATUM(result);
00441 }
00442 
00443 /*
00444  * nth_value
00445  * return the value of VE evaluated on the n-th row from the first
00446  * row of the window frame, per spec.
00447  */
00448 Datum
00449 window_nth_value(PG_FUNCTION_ARGS)
00450 {
00451     WindowObject winobj = PG_WINDOW_OBJECT();
00452     bool        const_offset;
00453     Datum       result;
00454     bool        isnull;
00455     int32       nth;
00456 
00457     nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
00458     if (isnull)
00459         PG_RETURN_NULL();
00460     const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
00461 
00462     if (nth <= 0)
00463         ereport(ERROR,
00464                 (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
00465                  errmsg("argument of nth_value must be greater than zero")));
00466 
00467     result = WinGetFuncArgInFrame(winobj, 0,
00468                                   nth - 1, WINDOW_SEEK_HEAD, const_offset,
00469                                   &isnull, NULL);
00470     if (isnull)
00471         PG_RETURN_NULL();
00472 
00473     PG_RETURN_DATUM(result);
00474 }