postgresql/src/backend/utils/adt/windowfuncs.c

475 lines
10 KiB
C

/*-------------------------------------------------------------------------
*
* windowfuncs.c
* Standard window functions defined in SQL spec.
*
* Portions Copyright (c) 2000-2017, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
* src/backend/utils/adt/windowfuncs.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/builtins.h"
#include "windowapi.h"
/*
* ranking process information
*/
typedef struct rank_context
{
int64 rank; /* current rank */
} rank_context;
/*
* ntile process information
*/
typedef struct
{
int32 ntile; /* current result */
int64 rows_per_bucket; /* row number of current bucket */
int64 boundary; /* how many rows should be in the bucket */
int64 remainder; /* (total rows) % (bucket num) */
} ntile_context;
static bool rank_up(WindowObject winobj);
static Datum leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool withoffset, bool withdefault);
/*
* utility routine for *_rank functions.
*/
static bool
rank_up(WindowObject winobj)
{
bool up = false; /* should rank increase? */
int64 curpos = WinGetCurrentPosition(winobj);
rank_context *context;
context = (rank_context *)
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
if (context->rank == 0)
{
/* first call: rank of first row is always 1 */
Assert(curpos == 0);
context->rank = 1;
}
else
{
Assert(curpos > 0);
/* do current and prior tuples match by ORDER BY clause? */
if (!WinRowsArePeers(winobj, curpos - 1, curpos))
up = true;
}
/* We can advance the mark, but only *after* access to prior row */
WinSetMarkPosition(winobj, curpos);
return up;
}
/*
* row_number
* just increment up from 1 until current partition finishes.
*/
Datum
window_row_number(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
int64 curpos = WinGetCurrentPosition(winobj);
WinSetMarkPosition(winobj, curpos);
PG_RETURN_INT64(curpos + 1);
}
/*
* rank
* Rank changes when key columns change.
* The new rank number is the current row number.
*/
Datum
window_rank(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
rank_context *context;
bool up;
up = rank_up(winobj);
context = (rank_context *)
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
if (up)
context->rank = WinGetCurrentPosition(winobj) + 1;
PG_RETURN_INT64(context->rank);
}
/*
* dense_rank
* Rank increases by 1 when key columns change.
*/
Datum
window_dense_rank(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
rank_context *context;
bool up;
up = rank_up(winobj);
context = (rank_context *)
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
if (up)
context->rank++;
PG_RETURN_INT64(context->rank);
}
/*
* percent_rank
* return fraction between 0 and 1 inclusive,
* which is described as (RK - 1) / (NR - 1), where RK is the current row's
* rank and NR is the total number of rows, per spec.
*/
Datum
window_percent_rank(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
rank_context *context;
bool up;
int64 totalrows = WinGetPartitionRowCount(winobj);
Assert(totalrows > 0);
up = rank_up(winobj);
context = (rank_context *)
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
if (up)
context->rank = WinGetCurrentPosition(winobj) + 1;
/* return zero if there's only one row, per spec */
if (totalrows <= 1)
PG_RETURN_FLOAT8(0.0);
PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1));
}
/*
* cume_dist
* return fraction between 0 and 1 inclusive,
* which is described as NP / NR, where NP is the number of rows preceding or
* peers to the current row, and NR is the total number of rows, per spec.
*/
Datum
window_cume_dist(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
rank_context *context;
bool up;
int64 totalrows = WinGetPartitionRowCount(winobj);
Assert(totalrows > 0);
up = rank_up(winobj);
context = (rank_context *)
WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
if (up || context->rank == 1)
{
/*
* The current row is not peer to prior row or is just the first, so
* count up the number of rows that are peer to the current.
*/
int64 row;
context->rank = WinGetCurrentPosition(winobj) + 1;
/*
* start from current + 1
*/
for (row = context->rank; row < totalrows; row++)
{
if (!WinRowsArePeers(winobj, row - 1, row))
break;
context->rank++;
}
}
PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
}
/*
* ntile
* compute an exact numeric value with scale 0 (zero),
* ranging from 1 (one) to n, per spec.
*/
Datum
window_ntile(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
ntile_context *context;
context = (ntile_context *)
WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
if (context->ntile == 0)
{
/* first call */
int64 total;
int32 nbuckets;
bool isnull;
total = WinGetPartitionRowCount(winobj);
nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull));
/*
* per spec: If NT is the null value, then the result is the null
* value.
*/
if (isnull)
PG_RETURN_NULL();
/*
* per spec: If NT is less than or equal to 0 (zero), then an
* exception condition is raised.
*/
if (nbuckets <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
errmsg("argument of ntile must be greater than zero")));
context->ntile = 1;
context->rows_per_bucket = 0;
context->boundary = total / nbuckets;
if (context->boundary <= 0)
context->boundary = 1;
else
{
/*
* If the total number is not divisible, add 1 row to leading
* buckets.
*/
context->remainder = total % nbuckets;
if (context->remainder != 0)
context->boundary++;
}
}
context->rows_per_bucket++;
if (context->boundary < context->rows_per_bucket)
{
/* ntile up */
if (context->remainder != 0 && context->ntile == context->remainder)
{
context->remainder = 0;
context->boundary -= 1;
}
context->ntile += 1;
context->rows_per_bucket = 1;
}
PG_RETURN_INT32(context->ntile);
}
/*
* leadlag_common
* common operation of lead() and lag()
* For lead() forward is true, whereas for lag() it is false.
* withoffset indicates we have an offset second argument.
* withdefault indicates we have a default third argument.
*/
static Datum
leadlag_common(FunctionCallInfo fcinfo,
bool forward, bool withoffset, bool withdefault)
{
WindowObject winobj = PG_WINDOW_OBJECT();
int32 offset;
bool const_offset;
Datum result;
bool isnull;
bool isout;
if (withoffset)
{
offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
if (isnull)
PG_RETURN_NULL();
const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
}
else
{
offset = 1;
const_offset = true;
}
result = WinGetFuncArgInPartition(winobj, 0,
(forward ? offset : -offset),
WINDOW_SEEK_CURRENT,
const_offset,
&isnull, &isout);
if (isout)
{
/*
* target row is out of the partition; supply default value if
* provided. otherwise it'll stay NULL
*/
if (withdefault)
result = WinGetFuncArgCurrent(winobj, 2, &isnull);
}
if (isnull)
PG_RETURN_NULL();
PG_RETURN_DATUM(result);
}
/*
* lag
* returns the value of VE evaluated on a row that is 1
* row before the current row within a partition,
* per spec.
*/
Datum
window_lag(PG_FUNCTION_ARGS)
{
return leadlag_common(fcinfo, false, false, false);
}
/*
* lag_with_offset
* returns the value of VE evaluated on a row that is OFFSET
* rows before the current row within a partition,
* per spec.
*/
Datum
window_lag_with_offset(PG_FUNCTION_ARGS)
{
return leadlag_common(fcinfo, false, true, false);
}
/*
* lag_with_offset_and_default
* same as lag_with_offset but accepts default value
* as its third argument.
*/
Datum
window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
{
return leadlag_common(fcinfo, false, true, true);
}
/*
* lead
* returns the value of VE evaluated on a row that is 1
* row after the current row within a partition,
* per spec.
*/
Datum
window_lead(PG_FUNCTION_ARGS)
{
return leadlag_common(fcinfo, true, false, false);
}
/*
* lead_with_offset
* returns the value of VE evaluated on a row that is OFFSET
* number of rows after the current row within a partition,
* per spec.
*/
Datum
window_lead_with_offset(PG_FUNCTION_ARGS)
{
return leadlag_common(fcinfo, true, true, false);
}
/*
* lead_with_offset_and_default
* same as lead_with_offset but accepts default value
* as its third argument.
*/
Datum
window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
{
return leadlag_common(fcinfo, true, true, true);
}
/*
* first_value
* return the value of VE evaluated on the first row of the
* window frame, per spec.
*/
Datum
window_first_value(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
Datum result;
bool isnull;
result = WinGetFuncArgInFrame(winobj, 0,
0, WINDOW_SEEK_HEAD, true,
&isnull, NULL);
if (isnull)
PG_RETURN_NULL();
PG_RETURN_DATUM(result);
}
/*
* last_value
* return the value of VE evaluated on the last row of the
* window frame, per spec.
*/
Datum
window_last_value(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
Datum result;
bool isnull;
result = WinGetFuncArgInFrame(winobj, 0,
0, WINDOW_SEEK_TAIL, true,
&isnull, NULL);
if (isnull)
PG_RETURN_NULL();
PG_RETURN_DATUM(result);
}
/*
* nth_value
* return the value of VE evaluated on the n-th row from the first
* row of the window frame, per spec.
*/
Datum
window_nth_value(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
bool const_offset;
Datum result;
bool isnull;
int32 nth;
nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
if (isnull)
PG_RETURN_NULL();
const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
if (nth <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
errmsg("argument of nth_value must be greater than zero")));
result = WinGetFuncArgInFrame(winobj, 0,
nth - 1, WINDOW_SEEK_HEAD, const_offset,
&isnull, NULL);
if (isnull)
PG_RETURN_NULL();
PG_RETURN_DATUM(result);
}