给Mysql加自定义函数计算百分位数(percentile)。
2017-07-06 16:16
567 查看
百分位数(percentile)的详细定义见百度百科。
在这里我用一个通俗的例子来补充解释, 例如今年有900万人参加了高考,自然有900万个成绩,某个学校计划择优招生分数最高的前900个,那么分数线应该是多少呢,这就涉及到百分位的计算,这个学校录取的百分比就是万分之一,分数线的位置在万分之九千九百九十九,化成百分比就是99.99%,那对应这个百分比位置的具体数值是多少呢,这个说的就是百分位(percentile)。
Mysql中要实现根据若干条记录的某个字段获取指定百分位的值,用常规的自定义函数似乎行不通。我们用C/C++语言写一个mysql插件。
udf_percentile.cc, 这是插件的源代码。
将以上源代码编译成.so文件
在ubuntu下,如果没有/usr/include/mysql, 需要先安装libmysqlclient-dev:sudo apt-get install libmysqlclient-dev。如果是mysql5.x,你可以下载我编译好的so文件。
将so文件拷贝到mysql的插件目录下:
以管理员身份进入mysql,注册自定义函数:
使用方法:
0.9991就是百分位(99.91%),5是指结果的精度(小数位数)。
没有mysql服务的管理权限的,只能通过程序实现了,最后附上percentile的JAVA和JS实现。
JAVA:
JS:
在这里我用一个通俗的例子来补充解释, 例如今年有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自定义函数计算年龄
- 使用存储过程代替自定义函数计算CP95
- MySql自定义函数
- 深入mysql创建自定义函数与存储过程的详解
- [MSSQL]日期集合-周、月、季度、年等计算以及自定义函数
- mysql自定义循环函数
- MySQL 自定义函数CREATE FUNCTION示例
- mysql通过自定义函数查询一级菜单
- MySQL 自定义函数CREATE FUNCTION实例
- php自定义函数: 计算两个时间日期相隔的天数,时,分,秒
- mysql 自定义函数 实现字符串匹配
- mysql创建自定义函数
- MySQL中计算sql语句影响行数的函数
- Mysql中的自定义函数和自定义过程
- mysql日期时间计算函数
- MYSQL计算时间间隔TimeStampDiff()函数用法
- MySQL中文汉字转拼音的自定义函数和使用实例(首字的首字母)
- Mysql学习笔记(七)mysql编程基础之自定义函数。
- 我的MYSQL学习心得(十) 自定义存储过程和函数
- mysql利用自定义变量实现分析函数