您的位置:首页 > 数据库 > MySQL

给Mysql加自定义函数计算百分位数(percentile)。

2017-07-06 16:16 567 查看
百分位数(percentile)的详细定义见百度百科

在这里我用一个通俗的例子来补充解释, 例如今年有900万人参加了高考,自然有900万个成绩,某个学校计划择优招生分数最高的前900个,那么分数线应该是多少呢,这就涉及到百分位的计算,这个学校录取的百分比就是万分之一,分数线的位置在万分之九千九百九十九,化成百分比就是99.99%,那对应这个百分比位置的具体数值是多少呢,这个说的就是百分位(percentile)。

Mysql中要实现根据若干条记录的某个字段获取指定百分位的值,用常规的自定义函数似乎行不通。我们用C/C++语言写一个mysql插件。

udf_percentile.cc, 这是插件的源代码。

/*
returns the percentile of the values in a distribution

input parameters:
data (real or int)
desired percentile of result, 0-1 (double)
decimals of output (optional, int)

output:
percentile value of the distribution (real)

compiling
gcc -fPIC -Wall -I /usr/include/mysql51/mysql/ -shared -o udf_percentile.so  udf_percentile.cc
udf_percentile.so /usr/lib64/mysql/plugin/

registering the function:
CREATE AGGREGATE FUNCTION percentile RETURNS REAL SONAME 'udf_percentile.so';

getting rid of the function:
DROP FUNCTION percentile;

hat tip to Jan Steemann's udf_median function http://mysql-udf.sourceforge.net/ */

#ifdef STANDARD
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __WIN__
typedef unsigned __int64 ulonglong;
typedef __int64 longlong;
#else
typedef unsigned long long ulonglong;
typedef long long longlong;
#endif /*__WIN__*/
#else
#include <my_global.h>
#include <my_sys.h>
#endif
#include <mysql.h>
#include <m_ctype.h>
#include <m_string.h>

#ifdef HAVE_DLOPEN

#define BUFFERSIZE 1024

extern "C" {
my_bool percentile_init( UDF_INIT* initid, UDF_ARGS* args, char* message );
void percentile_deinit( UDF_INIT* initid );
void percentile_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
void percentile_clear(UDF_INIT* initid, char *is_null, char *error);
void percentile_add( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
double percentile( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char *error );
}

struct percentile_data
{
unsigned long count;
unsigned long abscount;
unsigned long pages;
double *values;
};

my_bool percentile_init( UDF_INIT* initid, UDF_ARGS* args, char* message )
{

if ( args->arg_count < 2 || args->arg_count>3)
{
strcpy(message,"wrong number of arguments: percentile() requires two - three arguments");
return 1;
}

if (args->arg_type[0]!=REAL_RESULT && args->arg_type[0]!=INT_RESULT)
{
strcpy(message,"percentile() requires a real or int as parameter 1");
return 1;
}

if (args->arg_type[1]!=REAL_RESULT && args->arg_type[1]!=INT_RESULT && args->arg_type[1]!=DECIMAL_RESULT)
{
asprintf(&message,"percentile() requires a real/int as parameter 2, instead %d",args->arg_type[1]);
return 1;
}

if (args->arg_count>2 && args->arg_type[2]!=INT_RESULT)
{
strcpy(message,"percentile() requires an int as parameter 3");
return 1;
}

initid->decimals=2;

if (args->arg_count==3 && (*((ulong*)args->args[2])<=16))
{
initid->decimals=*((ulong*)args->args[2]);
}

percentile_data *buffer = new percentile_data;
buffer->count = 0;
buffer->abscount=0;
buffer->pages = 1;
buffer->values = NULL;

initid->maybe_null    = 1;
initid->max_length    = 32;
initid->ptr = (char*)buffer;

return 0;
}

void percentile_deinit( UDF_INIT* initid )
{
percentile_data *buffer = (percentile_data*)initid->ptr;

if (buffer->values != NULL)
{
free(buffer->values);
buffer->values=NULL;
}
delete initid->ptr;
}

void percentile_clear(UDF_INIT* initid, char *is_null, char *error){
percentile_data *buffer = (percentile_data*)initid->ptr;
buffer->count = 0;
buffer->abscount=0;
buffer->pages = 1;
*is_null = 0;
*error = 0;

if (buffer->values != NULL)
{
free(buffer->values);
buffer->values=NULL;
}

buffer->values=(double *) malloc(BUFFERSIZE*sizeof(double));
}

void percentile_reset( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* is_error )
{
percentile_clear(initid, is_null, is_error);
percentile_add( initid, args, is_null, is_error );
}

void percentile_add( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* is_error )
{
if (args->args[0]!=NULL)
{
percentile_data *buffer = (percentile_data*)initid->ptr;
if (buffer->count>=BUFFERSIZE)
{
buffer->pages++;
buffer->count=0;
buffer->values=(double *) realloc(buffer->values,BUFFERSIZE*buffer->pages*sizeof(double));
}
if(args->arg_type[0]==INT_RESULT){
buffer->values[buffer->abscount++] = (double) *((longlong*)args->args[0]);
}
else{
buffer->values[buffer->abscount++] = *((double*)args->args[0]);
}
buffer->count++;
}
}

int compare_doubles (const void *a, const void *b)
{
const double *da = (const double *) a;
const double *db = (const double *) b;

return (*da > *db) - (*da < *db);
}

double percentile( UDF_INIT* initid, UDF_ARGS* args, char* is_null, char* is_error )
{
percentile_data* buffer = (percentile_data*)initid->ptr;
double pctile;

if (buffer->abscount==0 || *is_error!=0)
{
*is_null = 1;
return 0.0;
}

*is_null=0;
if (buffer->abscount==1)
{
return buffer->values[0];
}

qsort(buffer->values,buffer->abscount,sizeof(double),compare_doubles);
if(args->arg_type[1]==INT_RESULT){
pctile = (double) *((longlong*)args->args[1]);
}
else if(args->arg_type[1]==DECIMAL_RESULT){
pctile = atof(args->args[1]);
}
else{
pctile = *((double*)args->args[1]);
}

int n = buffer->abscount - 1;

if(pctile <=0){
return buffer->values[0];
}
if(pctile>=1){
return buffer->values
;
}
double i = n * pctile;
int ix = (int) i;

if (i == ix)
{
return buffer->values[ix];
} else
{
int j = ix + 1 >= n ? n : ix + 1;
return(buffer->values[ix]*(j - i) + buffer->values[j]*(i - ix));
}

}

#endif

将以上源代码编译成.so文件

gcc -fPIC -Wall -I /usr/include/mysql/ -shared -o udf_percentile.so udf_percentile.cc

在ubuntu下,如果没有/usr/include/mysql, 需要先安装libmysqlclient-dev:sudo apt-get install libmysqlclient-dev。如果是mysql5.x,你可以下载我编译好的so文件

将so文件拷贝到mysql的插件目录下:

sudo cp udf_percentile.so /usr/lib/mysql/plugin/

以管理员身份进入mysql,注册自定义函数:

CREATE AGGREGATE FUNCTION percentile RETURNS REAL SONAME 'udf_percentile.so';

使用方法:

SELECT percentile(score, 0.9991,5) FROM candidates;

0.9991就是百分位(99.91%),5是指结果的精度(小数位数)。

没有mysql服务的管理权限的,只能通过程序实现了,最后附上percentile的JAVA和JS实现。

JAVA:

public static double percentile(double[] data,double p){
int n = data.length;
Arrays.sort(data);
double px =  p*(n-1);
int i = (int)java.lang.Math.floor(px);
double g = px - i;
if(g==0){
return data[i];
}else{
return (1-g)*data[i]+g*data[i+1];
}
}

JS:

const percentile = (data, p) =>{
let n = data.length;
data.sort((a, b) => a - b);
let px =  p*(n-1);
let i = Math.floor(px);
let g = px - i;
if(g==0){
return data[i];
}else{
return (1-g)*data[i]+g*data[i+1];
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mysql percentile 百分位