Skip to content
Snippets Groups Projects
Select Git revision
  • benchmark-tools
  • postgres-lambda
  • master default
  • REL9_4_25
  • REL9_5_20
  • REL9_6_16
  • REL_10_11
  • REL_11_6
  • REL_12_1
  • REL_12_0
  • REL_12_RC1
  • REL_12_BETA4
  • REL9_4_24
  • REL9_5_19
  • REL9_6_15
  • REL_10_10
  • REL_11_5
  • REL_12_BETA3
  • REL9_4_23
  • REL9_5_18
  • REL9_6_14
  • REL_10_9
  • REL_11_4
23 results

windowfuncs.c

Blame
  • windowfuncs.c 10.12 KiB
    /*-------------------------------------------------------------------------
     *
     * windowfuncs.c
     *	  Standard window functions defined in SQL spec.
     *
     * Portions Copyright (c) 2000-2013, 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* acccess 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 evelulated 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);
    }