#include <string.h>
#include <stdlib.h>
-/* The following object is the session context for a single percentile()
-** function. We have to remember all input Y values until the very end.
+/* The following object is the group context for a single percentile()
+** aggregate. Remember all input Y values until the very end.
** Those values are accumulated in the Percentile.a[] array.
*/
typedef struct Percentile Percentile;
unsigned nUsed; /* Number of slots actually used in a[] */
char bSorted; /* True if a[] is already in sorted order */
char bKeepSorted; /* True if advantageous to keep a[] sorted */
- double rPct; /* 1.0 more than the value for P */
+ char bPctValid; /* True if rPct is valid */
+ double rPct; /* Fraction. 0.0 to 1.0 */
double *a; /* Array of Y values */
};
+/* Details of each function in the percentile family */
+typedef struct PercentileFunc PercentileFunc;
+struct PercentileFunc {
+ const char *zName; /* Function name */
+ char nArg; /* Number of arguments */
+ char mxFrac; /* Maximum value of the "fraction" input */
+ char bDiscrete; /* True for percentile_disc() */
+};
+static const PercentileFunc aPercentFunc[] = {
+ { "median", 1, 1, 0 },
+ { "percentile", 2, 100, 0 },
+ { "percentile_cont", 2, 1, 0 },
+ { "percentile_disc", 2, 1, 1 },
+};
+
/*
** Return TRUE if the input floating-point number is an infinity.
*/
if( argc==1 ){
/* Requirement 13: median(Y) is the same as percentile(Y,50). */
- rPct = 50.0;
+ rPct = 0.5;
}else{
/* Requirement 3: P must be a number between 0 and 100 */
+ PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx);
eType = sqlite3_value_numeric_type(argv[1]);
- rPct = sqlite3_value_double(argv[1]);
+ rPct = sqlite3_value_double(argv[1])/(double)pFunc->mxFrac;
if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT)
- || rPct<0.0 || rPct>100.0 ){
- sqlite3_result_error(pCtx, "2nd argument to percentile() is not "
- "a number between 0.0 and 100.0", -1);
+ || rPct<0.0 || rPct>1.0
+ ){
+ char *zMsg;
+ zMsg = sqlite3_mprintf("the fraction argument to %s()"
+ " is not between 0.0 and %.1f",
+ pFunc->zName, (double)pFunc->mxFrac);
+ sqlite3_result_error(pCtx, zMsg, -1);
+ sqlite3_free(zMsg);
return;
}
}
/* Remember the P value. Throw an error if the P value is different
** from any prior row, per Requirement (2). */
- if( p->rPct==0.0 ){
- p->rPct = rPct+1.0;
- }else if( !percentSameValue(p->rPct,rPct+1.0) ){
- sqlite3_result_error(pCtx, "2nd argument to percentile() is not the "
- "same for all input rows", -1);
+ if( !p->bPctValid ){
+ p->rPct = rPct;
+ p->bPctValid = 1;
+ }else if( !percentSameValue(p->rPct,rPct) ){
+ PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx);
+ char *zMsg;
+ zMsg = sqlite3_mprintf("the fraction argument to %s()"
+ " is not the same for all input rows",
+ pFunc->zName);
+ sqlite3_result_error(pCtx, zMsg, -1);
+ sqlite3_free(zMsg);
return;
}
*/
static void percentCompute(sqlite3_context *pCtx, int bIsFinal){
Percentile *p;
+ PercentileFunc *pFunc = (PercentileFunc*)sqlite3_user_data(pCtx);
unsigned i1, i2;
double v1, v2;
double ix, vx;
}else{
percentAssertSorted(p);
}
- ix = (p->rPct-1.0)*(p->nUsed-1)*0.01;
+ ix = p->rPct*(p->nUsed-1);
i1 = (unsigned)ix;
- i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1;
- v1 = p->a[i1];
- v2 = p->a[i2];
- vx = v1 + (v2-v1)*(ix-i1);
+ if( pFunc->bDiscrete ){
+ vx = p->a[i1];
+ }else{
+ i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1;
+ v1 = p->a[i1];
+ v2 = p->a[i2];
+ vx = v1 + (v2-v1)*(ix-i1);
+ }
sqlite3_result_double(pCtx, vx);
}
if( bIsFinal ){
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
+ int i;
#ifdef SQLITE_STATIC_PERCENTILE
(void)pApi; /* Unused parameter */
#else
SQLITE_EXTENSION_INIT2(pApi);
#endif
(void)pzErrMsg; /* Unused parameter */
- rc = sqlite3_create_window_function(db, "percentile", 2,
- SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_SELFORDER1, 0,
- percentStep, percentFinal, percentValue, percentInverse, 0);
- if( rc==SQLITE_OK ){
- rc = sqlite3_create_window_function(db, "median", 1,
- SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_SELFORDER1, 0,
- percentStep, percentFinal, percentValue, percentInverse, 0);
+ for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){
+ rc = sqlite3_create_window_function(db,
+ aPercentFunc[i].zName,
+ aPercentFunc[i].nArg,
+ SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_SELFORDER1,
+ (void*)&aPercentFunc[i],
+ percentStep, percentFinal, percentValue, percentInverse, 0);
+ if( rc ) break;
}
return rc;
}
-C No\sprepare-time\spenality\sfor\sordered-set\saggregates\sfor\sapplications\sthat\ndo\snot\suse\sthem.
-D 2024-09-01T19:19:26.665
+C Implement\spercentile_cont()\sand\spercentile_disc().
+D 2024-09-01T20:24:24.177
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/misc/noop.c f1a21cc9b7a4e667e5c8458d80ba680b8bd4315a003f256006046879f679c5a0
F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d09ea61f
F ext/misc/pcachetrace.c f4227ce03fb16aa8d6f321b72dd051097419d7a028a9853af048bee7645cb405
-F ext/misc/percentile.c 98f7aab3607f6295b1c3168f763224e9de01a11c9c5c4cdd10f48418b89b52b4
+F ext/misc/percentile.c 76b29311319b6875ac6deb377c8338f651feeb5f929552994d18e3f0ffe6790d
F ext/misc/prefixes.c 82645f79229877afab08c8b08ca1e7fa31921280906b90a61c294e4f540cd2a6
F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47cc06c
F ext/misc/randomjson.c ef835fc64289e76ac4873b85fe12f9463a036168d7683cf2b773e36e6262c4ed
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
F test/pendingrace.test e99efc5ab3584da3dfc8cd6a0ec4e5a42214820574f5ea24ee93f1d84655f463
-F test/percentile.test 5c5b622035d5e2e9a23e8462b2f59f9f9364e736f3ddfb0ffe294b1854f9caad
+F test/percentile.test 827b3916a6db87d0521341cb828d0101a10de265e49c43503bbdb56e661f865f
F test/permutations.test 405542f1d659942994a6b38a9e024cf5cfd23eaa68c806aeb24a72d7c9186e80
F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f
F test/pragma.test 11cb9310c42f921918f7f563e3c0b6e70f9f9c3a6a1cf12af8fccb6c574f3882
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 7528ddcfdf155116353266e00e6408c526e29f04cb00ca2d9e84b105329b17c0
-R d3f3c0474c71bd75891856c6ee9d7573
+P e070c16d2183312e416ff6af770346041e4d3836c4db2c9ea6049f63fb0eaa07
+R ed42ef3fb6a6b57f98420c8f5c685fd3
U drh
-Z 9be7e64939656f5720b58c58cd9e6ba2
+Z a8f5cefc04272da03f15e22958022f9e
# Remove this line to create a well-formed Fossil manifest.
} {0 4.4}
do_test percentile-1.5 {
catchsql {SELECT round(percentile(x, 15+0.1*rowid),1) FROM t1}
-} {1 {2nd argument to percentile() is not the same for all input rows}}
+} {1 {the fraction argument to percentile() is not the same for all input rows}}
# Input values in a random order
#
#
do_test percentile-1.10 {
catchsql {SELECT percentile(x,null) FROM t1}
-} {1 {2nd argument to percentile() is not a number between 0.0 and 100.0}}
+} {1 {the fraction argument to percentile() is not between 0.0 and 100.0}}
do_test percentile-1.11 {
catchsql {SELECT percentile(x,'fifty') FROM t1}
-} {1 {2nd argument to percentile() is not a number between 0.0 and 100.0}}
+} {1 {the fraction argument to percentile() is not between 0.0 and 100.0}}
do_test percentile-1.12 {
catchsql {SELECT percentile(x,x'3530') FROM t1}
-} {1 {2nd argument to percentile() is not a number between 0.0 and 100.0}}
+} {1 {the fraction argument to percentile() is not between 0.0 and 100.0}}
# Second argument is out of range
#
do_test percentile-1.13 {
catchsql {SELECT percentile(x,-0.0000001) FROM t1}
-} {1 {2nd argument to percentile() is not a number between 0.0 and 100.0}}
+} {1 {the fraction argument to percentile() is not between 0.0 and 100.0}}
do_test percentile-1.14 {
catchsql {SELECT percentile(x,100.0000001) FROM t1}
-} {1 {2nd argument to percentile() is not a number between 0.0 and 100.0}}
+} {1 {the fraction argument to percentile() is not between 0.0 and 100.0}}
+do_test percentile-1.14.2 {
+ catchsql {SELECT percentile_cont(x,1.0000001) FROM t1}
+} {1 {the fraction argument to percentile_cont() is not between 0.0 and 1.0}}
+do_test percentile-1.14.3 {
+ catchsql {SELECT percentile_disc(x,1.0000001) FROM t1}
+} {1 {the fraction argument to percentile_disc() is not between 0.0 and 1.0}}
# First argument is not NULL and is not NUMERIC
#