On 5 October 2010 13:14, Hitoshi Harada <umi.tan...@gmail.com> wrote:
> 2010/10/5 Dean Rasheed <dean.a.rash...@gmail.com>:
>> On 5 October 2010 07:04, Hitoshi Harada <umi.tan...@gmail.com> wrote:
>> Extrapolating from few quick timing tests, even in the best case, on
>> my machine, it would take 7 days for the running median to use up
>> 100MB, and 8 years for it to use 2GB. So setting the tuplesort's
>> workMem to 2GB (only in the running median case) would actually be
>> safe in practice, and would prevent the temp file leak (for a few
>> years at least!). I feel dirty even suggesting that. Better ideas
>> anyone?
>
> So, I suggested to implement median as a *pure* window function aside
> from Pavel's aggregate function, and Greg suggested insertion
> capability of tuplesort. By this approach, we keep tuplesort to hold
> all the values in the current frame and can release it on the last of
> a partition (it's possible by window function API.) This is
> incremental addition of values and is far better than O(n^2 log(n))
> although I didn't estimate the order. Only when the frame head is
> moving down, we should re-initialize tuplesort and it is as slow as
> calling aggregate version per each row (but I think we can solve it
> somehow if looking precisely).
>

Possibly, but that sounds like a lot of work to get an efficient
algorithm. The 3 cases I see are:

1). Simple aggregate. Current algorithm is O(n log(n)) which is OK. It
could be better because a full sort is not strictly needed. As already
mentioned, a quickselect would be O(n).

2). Window without ORDER BY. This is actually basically the same as
(1), but called once per partition.

3). Window with ORDER BY (running median). The simplest algorithm is
O(n^2 log(n)). It could be tweaked to use an insertion sort, but that
would still be O(n^2), which is not a lot better for all the effort
that would be involved. In theory (perhaps with some kind of tree) it
ought to be possible to come up with an O(n log(n)) algorithm, but
that would be a lot of work.

In the meantime, the attached variation of the patch fixes the temp
file issue and will support all 3 cases. It gives OK performance for
(1) and (2), and poor performance for (3). That could be viewed as a
future development task, which perhaps the window function API would
help with. I think it would be a shame to drop support for (2) just
because we can't do (3) efficiently yet.

Regards,
Dean


> Regards,
>
> --
> Hitoshi Harada
>
*** ./doc/src/sgml/func.sgml.orig	2010-10-03 08:57:09.000000000 +0100
--- ./doc/src/sgml/func.sgml	2010-10-03 08:58:29.000000000 +0100
***************
*** 10386,10391 ****
--- 10386,10411 ----
  
       <row>
        <entry>
+        <indexterm>
+         <primary>Arithmetic median</primary>
+         <secondary>median</secondary>
+        </indexterm>
+        <function>median(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        <type>smallint</type>, <type>int</type>,
+        <type>bigint</type>, <type>real</type>, <type>double
+        precision</type>, or <type>numeric</type>
+       </entry>
+       <entry>
+        <type>double precision</type> for floating-point arguments,
+        otherwise <type>numeric</type>
+       </entry>
+       <entry>arithmetic median</entry>
+      </row>
+ 
+      <row>
+       <entry>
         <function>regr_avgx(<replaceable class="parameter">Y</replaceable>, <replaceable class="parameter">X</replaceable>)</function>
        </entry>
        <entry>
*** ./src/backend/utils/adt/Makefile.orig	2010-10-03 09:05:29.000000000 +0100
--- ./src/backend/utils/adt/Makefile	2010-10-03 09:05:57.000000000 +0100
***************
*** 19,25 ****
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
  	geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \
! 	misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rowtypes.o \
  	regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
--- 19,25 ----
  	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
  	geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \
! 	median.o misc.o nabstime.o name.o numeric.o numutils.o \
  	oid.o oracle_compat.o pseudotypes.o rowtypes.o \
  	regexp.o regproc.o ruleutils.o selfuncs.o \
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
*** ./src/backend/utils/adt/median.c.orig	2010-10-03 08:54:55.000000000 +0100
--- ./src/backend/utils/adt/median.c	2010-10-05 10:16:41.000000000 +0100
***************
*** 0 ****
--- 1,387 ----
+ #include "postgres.h"
+ 
+ #include "fmgr.h"
+ #include "miscadmin.h"
+ 
+ #include "catalog/pg_type.h"
+ #include "parser/parse_coerce.h"
+ #include "parser/parse_oper.h"
+ #include "utils/builtins.h"
+ #include "utils/lsyscache.h"
+ #include "utils/numeric.h"
+ #include "utils/tuplesort.h"
+ 
+ /*
+  * used as type of state variable median's function. It uses a 
+  * tuplesort as safe and inteligent storage. But we cannot to use
+  * a tuplesort under window aggregate context. 
+  */
+ typedef struct
+ {
+ 	int	nelems;		/* number of valid entries */
+ 	Tuplesortstate *sortstate;
+ 	bool	sorted;	/* true if the entries have been sorted */
+ 	bool	free_tuplesort;	/* true if the tuplesort should be freed */
+ 	FmgrInfo	cast_func_finfo;
+ 	bool	datumTypByVal;
+ } MedianAggState;
+ 
+ static void
+ freeDatum(Datum value, bool typByVal, bool isNull)
+ {
+ 	if (!typByVal && !isNull)
+ 	{
+ 		Pointer	s = DatumGetPointer(value);
+ 
+ 		pfree(s);
+ 	}
+ }
+ 
+ static MedianAggState *
+ makeMedianAggState(FunctionCallInfo fcinfo, Oid valtype, Oid targettype)
+ {
+ 	MemoryContext oldctx;
+ 	MemoryContext aggcontext;
+ 	MedianAggState *aggstate;
+ 	Oid	sortop,
+ 			castfunc;
+ 	CoercionPathType		pathtype;
+ 	int16	typlen;
+ 	bool		typbyval;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "median_transfn called in non-aggregate context");
+ 	}
+ 
+ 	oldctx = MemoryContextSwitchTo(aggcontext);
+ 
+ 	aggstate = (MedianAggState *) palloc0(sizeof(MedianAggState));
+ 
+ 	valtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ 	get_sort_group_operators(valtype,
+ 						    true, false, false,
+ 						    &sortop, NULL, NULL);
+ 
+ 	/* lookup necessary attributies of the datum type, used for datumFree */
+ 	get_typlenbyval(valtype, &typlen, &typbyval);
+ 	aggstate->datumTypByVal = typbyval;
+ 
+ 	/* initialize a tuplesort */
+ 	aggstate->sortstate = tuplesort_begin_datum(valtype,
+ 							sortop,
+ 							SORTBY_NULLS_DEFAULT,
+ 							work_mem, true);
+ 	aggstate->sorted = false;
+ 	aggstate->free_tuplesort = false; /* set in transfn, if necessary */
+ 
+ 	MemoryContextSwitchTo(oldctx);
+ 
+ 	if (valtype != targettype)
+ 	{
+ 		/* find a cast function */
+ 		pathtype = find_coercion_pathway(targettype, valtype,
+ 									COERCION_EXPLICIT,
+ 									&castfunc);
+ 		if (pathtype == COERCION_PATH_FUNC)
+ 		{
+ 			Assert(OidIsValid(castfunc));
+ 			fmgr_info_cxt(castfunc, &aggstate->cast_func_finfo,
+ 									    aggcontext);
+ 		} 
+ 		else if (pathtype == COERCION_PATH_RELABELTYPE)
+ 		{
+ 			aggstate->cast_func_finfo.fn_oid = InvalidOid;
+ 		}
+ 		else 
+ 			elog(ERROR, "no conversion function from %s %s",
+ 					 format_type_be(valtype),
+ 					 format_type_be(targettype));
+ 	}
+ 
+ 	return aggstate;
+ }
+ 
+ /*
+  *  append a non NULL value to tuplesort
+  */
+ static Datum
+ common_median_transfn(FunctionCallInfo fcinfo, Oid typoid, Oid targetoid)
+ {
+ 	MedianAggState *aggstate;
+ 
+ 	aggstate = PG_ARGISNULL(0) ? NULL : (MedianAggState *) PG_GETARG_POINTER(0);
+ 
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		bool	is_running_median = false;
+ 
+ 		if (aggstate == NULL)
+ 			aggstate = makeMedianAggState(fcinfo, typoid, targetoid);
+ 
+ 		if (aggstate->sorted)
+ 		{
+ 			/*
+ 			 * We have already done a sort and computed a median value, so
+ 			 * this must be a running median computation, over a WINDOW with
+ 			 * an ORDER BY clause.
+ 			 *
+ 			 * Transfer all the data to a new tuplesort, so that we can sort
+ 			 * it again in the final function. This is very inefficient, and
+ 			 * should probably be replaced with a better algorithm one day.
+ 			 *
+ 			 * For now, this is the easiest solution. We might as well try
+ 			 * to keep the new tuplesort in memory, since it will use up
+ 			 * memory so slowly that it's unlikely to ever be a problem.
+ 			 */
+ 			MemoryContext aggcontext;
+ 			MemoryContext oldctx;
+ 			Oid		valtype;
+ 			Oid		sortop;
+ 			Tuplesortstate *sortstate;
+ 			Datum	value;
+ 			bool	isNull;
+ 
+ 			if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 			{
+ 				/* cannot be called directly because of internal-type argument */
+ 				elog(ERROR, "median_transfn called in non-aggregate context");
+ 			}
+ 
+ 			oldctx = MemoryContextSwitchTo(aggcontext);
+ 
+ 			valtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ 			get_sort_group_operators(valtype,
+ 									 true, false, false,
+ 									 &sortop, NULL, NULL);
+ 
+ 			sortstate = tuplesort_begin_datum(valtype,
+ 											  sortop,
+ 											  SORTBY_NULLS_DEFAULT,
+ 											  2000000, true);
+ 
+ 			MemoryContextSwitchTo(oldctx);
+ 
+ 			tuplesort_rescan(aggstate->sortstate);
+ 			while (tuplesort_getdatum(aggstate->sortstate, true, &value, &isNull))
+ 			{
+ 				tuplesort_putdatum(sortstate, value, isNull);
+ 				freeDatum(value, aggstate->datumTypByVal, isNull);
+ 			}
+ 
+ 			tuplesort_end(aggstate->sortstate);
+ 
+ 			aggstate->sortstate = sortstate;
+ 			aggstate->sorted = false;
+ 			is_running_median = true;
+ 		}
+ 
+ 		/*
+ 		 * The final function should free the tuplesort unless this is a
+ 		 * running median computation. If this is the first valid value,
+ 		 * we don't actually know if it will be a running median, but it's
+ 		 * safe to not free in that case anyway, since the tuplesort won't
+ 		 * have used a temp file.
+ 		 */
+ 		aggstate->free_tuplesort = aggstate->nelems > 0 && !is_running_median;
+ 
+ 		tuplesort_putdatum(aggstate->sortstate, PG_GETARG_DATUM(1), false);
+ 		aggstate->nelems++;
+ 	}
+ 
+ 	PG_RETURN_POINTER(aggstate);
+ }
+ 
+ /*
+  * just wrappers to be opr sanity checks happy
+  */
+ Datum
+ median_numeric_transfn(PG_FUNCTION_ARGS)
+ {
+ 	return common_median_transfn(fcinfo, 
+ 						NUMERICOID, NUMERICOID);
+ }
+ 
+ Datum
+ median_int8_transfn(PG_FUNCTION_ARGS)
+ {
+ 	return common_median_transfn(fcinfo, 
+ 						INT8OID, NUMERICOID);
+ }
+ 
+ Datum
+ median_int4_transfn(PG_FUNCTION_ARGS)
+ {
+ 	return common_median_transfn(fcinfo, 
+ 						INT4OID, NUMERICOID);
+ }
+ 
+ Datum
+ median_int2_transfn(PG_FUNCTION_ARGS)
+ {
+ 	return common_median_transfn(fcinfo,
+ 						INT2OID, NUMERICOID);
+ }
+ 
+ Datum
+ median_double_transfn(PG_FUNCTION_ARGS)
+ {
+ 	return common_median_transfn(fcinfo,
+ 						FLOAT8OID, FLOAT8OID);
+ }
+ 
+ Datum
+ median_float_transfn(PG_FUNCTION_ARGS)
+ {
+ 	return common_median_transfn(fcinfo,
+ 						FLOAT4OID, FLOAT8OID);
+ }
+ 
+ /*
+  * Used for reading values from tuplesort. The value has to be
+  * double or cast function is defined (and used).
+  */
+ static double 
+ to_double(Datum value, FmgrInfo *cast_func_finfo)
+ {
+ 	/* when valtype is same as target type, returns directly */
+ 	if (cast_func_finfo->fn_oid == InvalidOid)
+ 		return DatumGetFloat8(value);
+ 
+ 	return DatumGetFloat8(FunctionCall1(cast_func_finfo, value));
+ }
+ 
+ /*
+  * Used as final function for median when result is double.
+  */
+ Datum
+ median_finalfn_double(PG_FUNCTION_ARGS)
+ {
+ 	MedianAggState *aggstate;
+ 
+ 	Assert(AggCheckCallContext(fcinfo, NULL));
+ 
+ 	aggstate = PG_ARGISNULL(0) ? NULL : (MedianAggState *) PG_GETARG_POINTER(0);
+ 
+ 	if (aggstate != NULL)
+ 	{
+ 		int	lidx;
+ 		int	hidx;
+ 		Datum	   value;
+ 		bool	isNull;
+ 		int		i = 1;
+ 		double	result = 0;
+ 
+ 		hidx = aggstate->nelems / 2 + 1;
+ 		lidx = (aggstate->nelems + 1) / 2;
+ 
+ 		tuplesort_performsort(aggstate->sortstate);
+ 		aggstate->sorted = true;
+ 
+ 		while (tuplesort_getdatum(aggstate->sortstate, true, &value, &isNull))
+ 		{
+ 			if (i++ == lidx)
+ 			{
+ 				result = to_double(value, &aggstate->cast_func_finfo);
+ 				freeDatum(value, aggstate->datumTypByVal, isNull);
+ 
+ 				if (lidx != hidx)
+ 				{
+ 					tuplesort_getdatum(aggstate->sortstate, true, &value, &isNull);
+ 					result = (result + to_double(value, &aggstate->cast_func_finfo)) / 2.0;
+ 					freeDatum(value, aggstate->datumTypByVal, isNull);
+ 				}
+ 				break;
+ 			}
+ 		}
+ 
+ 		if (aggstate->free_tuplesort)
+ 			tuplesort_end(aggstate->sortstate);
+ 
+ 		PG_RETURN_FLOAT8(result);
+ 	}
+ 
+ 	PG_RETURN_NULL();
+ }
+ 
+ /*
+  * Used for reading values from tuplesort. The value has to be
+  * Numeric or cast function is defined (and used).
+  */
+ static Numeric
+ to_numeric(Datum value, FmgrInfo *cast_func_finfo)
+ {
+ 	/* when valtype is same as target type, returns directly */
+ 	if (cast_func_finfo->fn_oid == InvalidOid)
+ 		return DatumGetNumeric(value);
+ 
+ 	return DatumGetNumeric(FunctionCall1(cast_func_finfo, value));
+ }
+ 
+ /*
+  * Used as final function for median when result is numeric.
+  */
+ Datum
+ median_finalfn_numeric(PG_FUNCTION_ARGS)
+ {
+ 	MedianAggState *aggstate;
+ 
+ 	Assert(AggCheckCallContext(fcinfo, NULL));
+ 
+ 	aggstate = PG_ARGISNULL(0) ? NULL : (MedianAggState *) PG_GETARG_POINTER(0);
+ 
+ 	if (aggstate != NULL)
+ 	{
+ 		int	lidx;
+ 		int	hidx;
+ 		Datum	   a_value;
+ 		bool	a_isNull;
+ 		int		i = 1;
+ 		Numeric result = NULL;		/* be compiler quiet */
+ 
+ 		hidx = aggstate->nelems / 2 + 1;
+ 		lidx = (aggstate->nelems + 1) / 2;
+ 
+ 		tuplesort_performsort(aggstate->sortstate);
+ 		aggstate->sorted = true;
+ 
+ 		while (tuplesort_getdatum(aggstate->sortstate, true, &a_value, &a_isNull))
+ 		{
+ 			if (i++ == lidx)
+ 			{
+ 				result = to_numeric(a_value, &aggstate->cast_func_finfo);
+ 
+ 				if (lidx != hidx)
+ 				{
+ 					Datum	b_value;
+ 					bool	b_isNull;
+ 					Numeric stack;
+ 
+ 					tuplesort_getdatum(aggstate->sortstate, true, &b_value, &b_isNull);
+ 
+ 					stack = to_numeric(b_value, &aggstate->cast_func_finfo);
+ 
+ 					stack = DatumGetNumeric(DirectFunctionCall2(numeric_add,
+ 												NumericGetDatum(stack),
+ 												NumericGetDatum(result)));
+ 					result = DatumGetNumeric(DirectFunctionCall2(numeric_div,
+ 												NumericGetDatum(stack),
+ 												DirectFunctionCall1(float4_numeric,
+ 																    Float4GetDatum(2.0))));
+ 					freeDatum(b_value, aggstate->datumTypByVal, b_isNull);
+ 				}
+ 				break;
+ 			}
+ 			else 
+ 				freeDatum(a_value, aggstate->datumTypByVal, a_isNull);
+ 		}
+ 
+ 		if (aggstate->free_tuplesort)
+ 			tuplesort_end(aggstate->sortstate);
+ 
+ 		PG_RETURN_NUMERIC(result);
+ 	}
+ 
+ 	PG_RETURN_NULL();
+ }
*** ./src/include/catalog/pg_aggregate.h.orig	2010-10-03 08:57:09.000000000 +0100
--- ./src/include/catalog/pg_aggregate.h	2010-10-03 08:54:23.000000000 +0100
***************
*** 226,231 ****
--- 226,239 ----
  /* text */
  DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	_null_ ));
  
+ /* median */
+ DATA(insert ( 3123	median_double_transfn	median_finalfn_double		0	2281	_null_ ));
+ DATA(insert ( 3124	median_float_transfn	median_finalfn_double		0	2281	_null_ ));
+ DATA(insert ( 3125	median_numeric_transfn	median_finalfn_numeric		0	2281	_null_ ));
+ DATA(insert ( 3126	median_int8_transfn	median_finalfn_numeric		0	2281	_null_ ));
+ DATA(insert ( 3127	median_int4_transfn	median_finalfn_numeric		0	2281	_null_ ));
+ DATA(insert ( 3128	median_int2_transfn	median_finalfn_numeric		0	2281	_null_ ));
+ 
  /*
   * prototypes for functions in pg_aggregate.c
   */
*** ./src/include/catalog/pg_proc.h.orig	2010-10-03 08:57:09.000000000 +0100
--- ./src/include/catalog/pg_proc.h	2010-10-03 08:54:23.000000000 +0100
***************
*** 4850,4855 ****
--- 4850,4884 ----
  DATA(insert OID = 3114 (  nth_value		PGNSP PGUID 12 1 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ ));
  DESCR("fetch the Nth row value");
  
+ /* median aggregate */
+ DATA(insert OID = 3115 ( median_numeric_transfn			PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_	median_numeric_transfn _null_ _null_ _null_ ));
+ DESCR("median transident function for numeric");
+ DATA(insert OID = 3116 ( median_int8_transfn			PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_	median_int8_transfn _null_ _null_ _null_ ));
+ DESCR("median transident function for bigint");
+ DATA(insert OID = 3117 ( median_int4_transfn			PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_	median_int4_transfn _null_ _null_ _null_ ));
+ DESCR("median transident function for int");
+ DATA(insert OID = 3118 ( median_int2_transfn			PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_	median_int2_transfn _null_ _null_ _null_ ));
+ DESCR("median transident function for smallint");
+ DATA(insert OID = 3119 ( median_double_transfn			PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 701" _null_ _null_ _null_ _null_	median_double_transfn _null_ _null_ _null_ ));
+ DESCR("median transident function for double precision");
+ DATA(insert OID = 3120 ( median_float_transfn			PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 700" _null_ _null_ _null_ _null_	median_float_transfn _null_ _null_ _null_ ));
+ DESCR("median transident function for real");
+ DATA(insert OID = 3121 ( median_finalfn_double			PGNSP PGUID 12 1 0 0 f f f f f i 1 0 701 "2281" _null_ _null_ _null_ _null_	median_finalfn_double _null_ _null_ _null_ ));
+ DESCR("median final function with double precision result");
+ DATA(insert OID = 3122 ( median_finalfn_numeric			PGNSP PGUID 12 1 0 0 f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_	median_finalfn_numeric _null_ _null_ _null_ ));
+ DESCR("median final function with numeric result");
+ DATA(insert OID = 3123 ( median					   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 701 "701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("median aggregate");
+ DATA(insert OID = 3124 ( median					   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 701 "700" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("median aggregate");
+ DATA(insert OID = 3125 ( median					   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("median aggregate");
+ DATA(insert OID = 3126 ( median					   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "20" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("median aggregate");
+ DATA(insert OID = 3127 ( median					   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "23" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("median aggregate");
+ DATA(insert OID = 3128 ( median					   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("median aggregate");
  
  /*
   * Symbolic values for provolatile column: these indicate whether the result
*** ./src/include/utils/builtins.h.orig	2010-10-03 08:57:09.000000000 +0100
--- ./src/include/utils/builtins.h	2010-10-03 08:54:23.000000000 +0100
***************
*** 934,939 ****
--- 934,947 ----
  extern Datum int8_avg(PG_FUNCTION_ARGS);
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
  extern Datum hash_numeric(PG_FUNCTION_ARGS);
+ extern Datum median_numeric_transfn(PG_FUNCTION_ARGS);
+ extern Datum median_int8_transfn(PG_FUNCTION_ARGS);
+ extern Datum median_int4_transfn(PG_FUNCTION_ARGS);
+ extern Datum median_int2_transfn(PG_FUNCTION_ARGS);
+ extern Datum median_double_transfn(PG_FUNCTION_ARGS);
+ extern Datum median_float_transfn(PG_FUNCTION_ARGS);
+ extern Datum median_finalfn_double(PG_FUNCTION_ARGS);
+ extern Datum median_finalfn_numeric(PG_FUNCTION_ARGS);
  
  /* ri_triggers.c */
  extern Datum RI_FKey_check_ins(PG_FUNCTION_ARGS);
*** ./src/test/regress/expected/numeric.out.orig	2010-10-03 08:57:09.000000000 +0100
--- ./src/test/regress/expected/numeric.out	2010-10-03 09:01:36.000000000 +0100
***************
*** 1372,1374 ****
--- 1372,1423 ----
   12345678901234567890
  (1 row)
  
+ -- median test
+ create table median_test (a int, b bigint, c smallint, d numeric, e double precision, f real);
+ insert into median_test select i,i,i,i,i,i from generate_series(1,10) g(i);
+ select median(a),
+        median(b),
+        median(c),
+        median(d),
+        median(e),
+        median(f) from median_test;
+        median       |       median       |       median       |       median       | median | median 
+ --------------------+--------------------+--------------------+--------------------+--------+--------
+  5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 |    5.5 |    5.5
+ (1 row)
+ 
+ truncate table median_test;
+ insert into median_test select i,i,i,i,i,i from generate_series(1,11) g(i);
+ select median(a),
+        median(b),
+        median(c),
+        median(d),
+        median(e),
+        median(f) from median_test;
+  median | median | median | median | median | median 
+ --------+--------+--------+--------+--------+--------
+       6 |      6 |      6 |      6 |      6 |      6
+ (1 row)
+ 
+ select median(a) over (order by a),
+        median(b) over (order by a),
+        median(c) over (order by a),
+        median(d) over (order by a),
+        median(e) over (order by a),
+        median(f) over (order by a) from median_test;
+        median       |       median       |       median       |       median       | median | median 
+ --------------------+--------------------+--------------------+--------------------+--------+--------
+                   1 |                  1 |                  1 |                  1 |      1 |      1
+  1.5000000000000000 | 1.5000000000000000 | 1.5000000000000000 | 1.5000000000000000 |    1.5 |    1.5
+                   2 |                  2 |                  2 |                  2 |      2 |      2
+  2.5000000000000000 | 2.5000000000000000 | 2.5000000000000000 | 2.5000000000000000 |    2.5 |    2.5
+                   3 |                  3 |                  3 |                  3 |      3 |      3
+  3.5000000000000000 | 3.5000000000000000 | 3.5000000000000000 | 3.5000000000000000 |    3.5 |    3.5
+                   4 |                  4 |                  4 |                  4 |      4 |      4
+  4.5000000000000000 | 4.5000000000000000 | 4.5000000000000000 | 4.5000000000000000 |    4.5 |    4.5
+                   5 |                  5 |                  5 |                  5 |      5 |      5
+  5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 |    5.5 |    5.5
+                   6 |                  6 |                  6 |                  6 |      6 |      6
+ (11 rows)
+ 
+ drop table median_test;
*** ./src/test/regress/sql/numeric.sql.orig	2010-10-03 08:57:09.000000000 +0100
--- ./src/test/regress/sql/numeric.sql	2010-10-03 09:02:43.000000000 +0100
***************
*** 824,826 ****
--- 824,852 ----
  select 12345678901234567890 / 123;
  select div(12345678901234567890, 123);
  select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123;
+ 
+ -- median test
+ create table median_test (a int, b bigint, c smallint, d numeric, e double precision, f real);
+ insert into median_test select i,i,i,i,i,i from generate_series(1,10) g(i);
+ select median(a),
+        median(b),
+        median(c),
+        median(d),
+        median(e),
+        median(f) from median_test;
+ truncate table median_test;
+ insert into median_test select i,i,i,i,i,i from generate_series(1,11) g(i);
+ select median(a),
+        median(b),
+        median(c),
+        median(d),
+        median(e),
+        median(f) from median_test;
+ select median(a) over (order by a),
+        median(b) over (order by a),
+        median(c) over (order by a),
+        median(d) over (order by a),
+        median(e) over (order by a),
+        median(f) over (order by a) from median_test;
+ 
+ drop table median_test;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to