]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Implement percentile_cont() and percentile_disc().
authordrh <>
Sun, 1 Sep 2024 20:24:24 +0000 (20:24 +0000)
committerdrh <>
Sun, 1 Sep 2024 20:24:24 +0000 (20:24 +0000)
FossilOrigin-Name: b1a93f67d6b21df6fe3247c9333fead61dd425574f66ea3eb06b80c2b06f616a

ext/misc/percentile.c
manifest
manifest.uuid
test/percentile.test

index cafc55fead61cf4184cf4a38f730baa4a9637416..1b6ff0b5911d8bd53a1dd33ad1d45ff13bd8e7ec 100644 (file)
 #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;
@@ -115,10 +115,26 @@ struct 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.
 */
@@ -192,15 +208,21 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
 
   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;
     }
   }
@@ -211,11 +233,17 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
 
   /* 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;
   }
 
@@ -391,6 +419,7 @@ static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){
 */
 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;
@@ -405,12 +434,16 @@ static void percentCompute(sqlite3_context *pCtx, int bIsFinal){
     }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 ){
@@ -436,19 +469,21 @@ int sqlite3_percentile_init(
   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;
 }
index 575b14e652161ed54e35a602473f65325ab09f61..72c50d4947f2974d50b7bb9c16ba09996afeb01b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-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
@@ -410,7 +410,7 @@ F ext/misc/nextchar.c 7877914c2a80c2f181dd04c3dbef550dfb54c93495dc03da2403b5dd58
 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
@@ -1517,7 +1517,7 @@ F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035c
 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
@@ -2211,8 +2211,8 @@ F vsixtest/vsixtest.tcl 6195aba1f12a5e10efc2b8c0009532167be5e301abe5b31385638080
 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.
index 5ee39e884beea05dad7bb5c48a17e64b9440186f..8048ca7545bc162d2a3c9e9831bf6f3b5c68dd4c 100644 (file)
@@ -1 +1 @@
-e070c16d2183312e416ff6af770346041e4d3836c4db2c9ea6049f63fb0eaa07
+b1a93f67d6b21df6fe3247c9333fead61dd425574f66ea3eb06b80c2b06f616a
index cef3835957435555b496d0d5153ab0df2af7f6cf..3207aea0961f6f52dbfba947cce4a5719252403e 100644 (file)
@@ -68,7 +68,7 @@ do_test percentile-1.4 {
 } {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
 #
@@ -105,22 +105,28 @@ do_test percentile-1.9 {
 #
 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
 #