Allow percentile() and median() to act as window functions.
FossilOrigin-Name: 94cf96af8fee55449080655bddf81cbf5c078a02d7bb5dd7e4903b36f83a8c07
This commit is contained in:
+2
-1
@@ -1182,6 +1182,7 @@ SHELL_DEP = \
|
||||
$(TOP)/ext/misc/ieee754.c \
|
||||
$(TOP)/ext/misc/memtrace.c \
|
||||
$(TOP)/ext/misc/pcachetrace.c \
|
||||
$(TOP)/ext/misc/percentile.c \
|
||||
$(TOP)/ext/misc/regexp.c \
|
||||
$(TOP)/ext/misc/series.c \
|
||||
$(TOP)/ext/misc/shathree.c \
|
||||
@@ -1390,7 +1391,7 @@ srctree-check: $(TOP)/tool/srctree-check.tcl
|
||||
|
||||
# Testing for a release
|
||||
#
|
||||
releasetest: srctree-check has_tclsh85
|
||||
releasetest: srctree-check has_tclsh85 verify-source
|
||||
$(TCLSH_CMD) $(TOP)/test/testrunner.tcl release $(TSTRNNR_OPTS)
|
||||
|
||||
# Minimal testing that runs in less than 3 minutes
|
||||
|
||||
@@ -2313,6 +2313,7 @@ SHELL_DEP = \
|
||||
$(TOP)\ext\misc\ieee754.c \
|
||||
$(TOP)\ext\misc\memtrace.c \
|
||||
$(TOP)\ext\misc\pcachetrace.c \
|
||||
$(TOP)\ext\misc\percentile.c \
|
||||
$(TOP)\ext\misc\regexp.c \
|
||||
$(TOP)\ext\misc\series.c \
|
||||
$(TOP)\ext\misc\shathree.c \
|
||||
|
||||
+181
-21
@@ -62,6 +62,29 @@
|
||||
**
|
||||
** (14) A separate percentile_cond(Y,X) function is the equivalent of
|
||||
** percentile(Y,X*100.0).
|
||||
**
|
||||
** (15) All three SQL functions implemented by this module can also be
|
||||
** used as window-functions.
|
||||
**
|
||||
** Implementation notes as of 2024-08-31:
|
||||
**
|
||||
** * The regular aggregate-function versions of the merge(), percentile(),
|
||||
** and percentile_cond() routines work by accumulating all values in
|
||||
** an array of doubles, then sorting that array using a quicksort
|
||||
** before computing the answer. Thus the runtime is O(NlogN) where
|
||||
** N is the number of rows of input.
|
||||
**
|
||||
** * For the window-function versions of these routines, the array of
|
||||
** inputs is sorted as soon as the first value is computed. Thereafter,
|
||||
** the array is kept in sorted order using an insert-sort. This
|
||||
** results in O(N*K) performance where K is the size of the window.
|
||||
** One can devise alternative implementations that give O(N*logN*logK)
|
||||
** performance, but they require more complex logic and data structures.
|
||||
** The developers have elected to keep the asymptotically slower
|
||||
** algorithm for now, for simplicity, under the theory that window
|
||||
** functions are seldom used and when they are, the window size K is
|
||||
** often small. The developers might revisit that decision later,
|
||||
** should the need arise.
|
||||
*/
|
||||
#include "sqlite3ext.h"
|
||||
SQLITE_EXTENSION_INIT1
|
||||
@@ -77,6 +100,8 @@ typedef struct Percentile Percentile;
|
||||
struct Percentile {
|
||||
unsigned nAlloc; /* Number of slots allocated for a[] */
|
||||
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 */
|
||||
double *a; /* Array of Y values */
|
||||
};
|
||||
@@ -84,7 +109,7 @@ struct Percentile {
|
||||
/*
|
||||
** Return TRUE if the input floating-point number is an infinity.
|
||||
*/
|
||||
static int isInfinity(double r){
|
||||
static int percentIsInfinity(double r){
|
||||
sqlite3_uint64 u;
|
||||
assert( sizeof(u)==sizeof(r) );
|
||||
memcpy(&u, &r, sizeof(u));
|
||||
@@ -92,13 +117,55 @@ static int isInfinity(double r){
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if two doubles differ by 0.001 or less
|
||||
** Return TRUE if two doubles differ by 0.001 or less.
|
||||
*/
|
||||
static int sameValue(double a, double b){
|
||||
static int percentSameValue(double a, double b){
|
||||
a -= b;
|
||||
return a>=-0.001 && a<=0.001;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* Verify that the elements of the Percentile p are in fact sorted.
|
||||
** Used for testing and debugging only.
|
||||
*/
|
||||
static void percentAssertSorted(Percentile *p){
|
||||
int i;
|
||||
for(i=p->nUsed-2; i>=0 && p->a[i]<=p->a[i+1]; i--){}
|
||||
assert( i<0 );
|
||||
}
|
||||
#else
|
||||
# define percentAssertSorted(X)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Search p (which must have p->bSorted) looking for an entry with
|
||||
** value y. Return the index of that entry.
|
||||
**
|
||||
** If bExact is true, return -1 if the entry is not found.
|
||||
**
|
||||
** If bExact is false, return the index at which a new entry with
|
||||
** value y should be insert in order to keep the values in sorted
|
||||
** order. The smallest return value in this case will be 0, and
|
||||
** the largest return value will be p->nUsed.
|
||||
*/
|
||||
static int percentBinarySearch(Percentile *p, double y, int bExact){
|
||||
int iFirst = 0; /* First element of search range */
|
||||
int iLast = p->nUsed - 1; /* Last element of search range */
|
||||
while( iLast>=iFirst ){
|
||||
int iMid = (iFirst+iLast)/2;
|
||||
double x = p->a[iMid];
|
||||
if( x<y ){
|
||||
iFirst = iMid + 1;
|
||||
}else if( x>y ){
|
||||
iLast = iMid - 1;
|
||||
}else{
|
||||
return iMid;
|
||||
}
|
||||
}
|
||||
if( bExact ) return -1;
|
||||
return iFirst;
|
||||
}
|
||||
|
||||
/*
|
||||
** The "step" function for percentile(Y,P) is called once for each
|
||||
** input row.
|
||||
@@ -144,7 +211,7 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
|
||||
** from any prior row, per Requirement (2). */
|
||||
if( p->rPct==0.0 ){
|
||||
p->rPct = rPct+1.0;
|
||||
}else if( !sameValue(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);
|
||||
return;
|
||||
@@ -164,7 +231,7 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
|
||||
|
||||
/* Throw an error if the Y value is infinity or NaN */
|
||||
y = sqlite3_value_double(argv[0]);
|
||||
if( isInfinity(y) ){
|
||||
if( percentIsInfinity(y) ){
|
||||
sqlite3_result_error(pCtx, "Inf input to percentile()", -1);
|
||||
return;
|
||||
}
|
||||
@@ -182,11 +249,37 @@ static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){
|
||||
p->nAlloc = n;
|
||||
p->a = a;
|
||||
}
|
||||
p->a[p->nUsed++] = y;
|
||||
if( p->nUsed==0 ){
|
||||
p->a[p->nUsed++] = y;
|
||||
p->bSorted = 1;
|
||||
}else if( !p->bSorted || y>=p->a[p->nUsed-1] ){
|
||||
p->a[p->nUsed++] = y;
|
||||
}else if( p->bKeepSorted ){
|
||||
int i;
|
||||
percentAssertSorted(p);
|
||||
i = percentBinarySearch(p, y, 0);
|
||||
if( i<p->nUsed ){
|
||||
memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0]));
|
||||
}
|
||||
p->a[i] = y;
|
||||
p->nUsed++;
|
||||
}else{
|
||||
p->a[p->nUsed++] = y;
|
||||
p->bSorted = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Sort an array of doubles.
|
||||
**
|
||||
** Algorithm: quicksort
|
||||
**
|
||||
** This is implemented separately rather than using the qsort() routine
|
||||
** from the standard library because:
|
||||
**
|
||||
** (1) To avoid a dependency on qsort()
|
||||
** (2) To avoid the function call to the comparison routine for each
|
||||
** comparison.
|
||||
*/
|
||||
static void sortDoubles(double *a, int n){
|
||||
int iLt; /* Entries with index less than iLt are less than rPivot */
|
||||
@@ -234,11 +327,61 @@ static void sortDoubles(double *a, int n){
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Called to compute the final output of percentile() and to clean
|
||||
** up all allocated memory.
|
||||
** The "inverse" function for percentile(Y,P) is called to remove a
|
||||
** row that was previously inserted by "step".
|
||||
*/
|
||||
static void percentFinal(sqlite3_context *pCtx){
|
||||
static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){
|
||||
Percentile *p;
|
||||
int eType;
|
||||
double y;
|
||||
int i;
|
||||
assert( argc==2 || argc==1 );
|
||||
|
||||
/* Allocate the session context. */
|
||||
p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p));
|
||||
assert( p!=0 );
|
||||
|
||||
/* Ignore rows for which Y is NULL */
|
||||
eType = sqlite3_value_type(argv[0]);
|
||||
if( eType==SQLITE_NULL ) return;
|
||||
|
||||
/* If not NULL, then Y must be numeric. Otherwise throw an error.
|
||||
** Requirement 4 */
|
||||
if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ignore the Y value if it is infinity or NaN */
|
||||
y = sqlite3_value_double(argv[0]);
|
||||
if( percentIsInfinity(y) ){
|
||||
return;
|
||||
}
|
||||
if( p->bSorted==0 ){
|
||||
sortDoubles(p->a, p->nUsed);
|
||||
p->bSorted = 1;
|
||||
}else{
|
||||
percentAssertSorted(p);
|
||||
}
|
||||
p->bKeepSorted = 1;
|
||||
|
||||
/* Find and remove the row */
|
||||
i = percentBinarySearch(p, y, 1);
|
||||
if( i>=0 ){
|
||||
p->nUsed--;
|
||||
if( i<p->nUsed ){
|
||||
memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0]));
|
||||
}
|
||||
}
|
||||
percentAssertSorted(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Compute the final output of percentile(). Clean up all allocated
|
||||
** memory if and only if bIsFinal is true.
|
||||
*/
|
||||
static void percentCompute(sqlite3_context *pCtx, int bIsFinal){
|
||||
Percentile *p;
|
||||
unsigned i1, i2;
|
||||
double v1, v2;
|
||||
@@ -247,7 +390,12 @@ static void percentFinal(sqlite3_context *pCtx){
|
||||
if( p==0 ) return;
|
||||
if( p->a==0 ) return;
|
||||
if( p->nUsed ){
|
||||
sortDoubles(p->a, p->nUsed);
|
||||
if( p->bSorted==0 ){
|
||||
sortDoubles(p->a, p->nUsed);
|
||||
p->bSorted = 1;
|
||||
}else{
|
||||
percentAssertSorted(p);
|
||||
}
|
||||
ix = (p->rPct-1.0)*(p->nUsed-1)*0.01;
|
||||
i1 = (unsigned)ix;
|
||||
i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1;
|
||||
@@ -256,10 +404,19 @@ static void percentFinal(sqlite3_context *pCtx){
|
||||
vx = v1 + (v2-v1)*(ix-i1);
|
||||
sqlite3_result_double(pCtx, vx);
|
||||
}
|
||||
sqlite3_free(p->a);
|
||||
memset(p, 0, sizeof(*p));
|
||||
if( bIsFinal ){
|
||||
sqlite3_free(p->a);
|
||||
memset(p, 0, sizeof(*p));
|
||||
}else{
|
||||
p->bKeepSorted = 1;
|
||||
}
|
||||
}
|
||||
static void percentFinal(sqlite3_context *pCtx){
|
||||
percentCompute(pCtx, 1);
|
||||
}
|
||||
static void percentValue(sqlite3_context *pCtx){
|
||||
percentCompute(pCtx, 0);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
@@ -272,18 +429,21 @@ int sqlite3_percentile_init(
|
||||
int rc = SQLITE_OK;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
(void)pzErrMsg; /* Unused parameter */
|
||||
rc = sqlite3_create_function(db, "percentile", 2,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
|
||||
0, percentStep, percentFinal);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_function(db, "median", 1,
|
||||
rc = sqlite3_create_window_function(db, "percentile", 2,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
|
||||
0, percentStep, percentFinal);
|
||||
percentStep, percentFinal,
|
||||
percentValue, percentInverse, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_window_function(db, "median", 1,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
|
||||
percentStep, percentFinal,
|
||||
percentValue, percentInverse, 0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_function(db, "percentile_cont", 2,
|
||||
rc = sqlite3_create_window_function(db, "percentile_cont", 2,
|
||||
SQLITE_UTF8|SQLITE_INNOCUOUS, &percentStep,
|
||||
0, percentStep, percentFinal);
|
||||
percentStep, percentFinal,
|
||||
percentValue, percentInverse, 0);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -764,6 +764,7 @@ SHELL_DEP = \
|
||||
$(TOP)/ext/misc/ieee754.c \
|
||||
$(TOP)/ext/misc/memtrace.c \
|
||||
$(TOP)/ext/misc/pcachetrace.c \
|
||||
$(TOP)/ext/misc/percentile.c \
|
||||
$(TOP)/ext/misc/regexp.c \
|
||||
$(TOP)/ext/misc/series.c \
|
||||
$(TOP)/ext/misc/shathree.c \
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
C Do\snot\sfail\sthe\somittest\sif\sICU\slibraries\sare\snot\sinstalled.
|
||||
D 2024-08-30T17:33:25.286
|
||||
C Allow\spercentile()\sand\smedian()\sto\sact\sas\swindow\sfunctions.
|
||||
D 2024-08-31T18:08:31.388
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
F Makefile.in 77627cbeeffe23606da5663458c0f8bbb86d28e2aca270a5d9b0db8e39a54bb2
|
||||
F Makefile.in 394e8c4b17d7a25b26a379573cb74bac569e6e4e9b5f1e2cf6efd53866867054
|
||||
F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6
|
||||
F Makefile.msc 47dfd26a401c5a33e237ecfe126c996977f34ac44e44a1a1ebf2204abb89d483
|
||||
F Makefile.msc 8dc55cc46fabd6a01afca3a5829c4cb69b99537e42143ca6f0bf6e811b9f5068
|
||||
F README.md 5b678e264236788390d11991f2c0052bd73f19790173883fc56d638bcb849154
|
||||
F VERSION 0db40f92c04378404eb45bff93e9e42c148c7e54fd3da99469ed21e22411f5a6
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
@@ -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 af1941dc87d45dd0c2698a3087fbfe9ee0d157e5e72da521430c4b784abcbe81
|
||||
F ext/misc/percentile.c 46627b7495c69344d384f667bb6c80ba2c4aeb779997a4e22fea1a39cd20beb9
|
||||
F ext/misc/prefixes.c 82645f79229877afab08c8b08ca1e7fa31921280906b90a61c294e4f540cd2a6
|
||||
F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47cc06c
|
||||
F ext/misc/randomjson.c ef835fc64289e76ac4873b85fe12f9463a036168d7683cf2b773e36e6262c4ed
|
||||
@@ -685,7 +685,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
|
||||
F main.mk 40f8cd07372d7eed2fe33403a42b82c3f5d2d46b59480444384326db70bb33f1
|
||||
F main.mk de83918046afeb0ac4239e88511623f3ef2a0c41aab0e2b401e0275f4fd24a8e
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421
|
||||
@@ -718,7 +718,7 @@ F src/delete.c 444c4d1eaac40103461e3b6f0881846dd3aafc1cec1dd169d3482fa331667da7
|
||||
F src/expr.c 6d5f2c38fe3ec06a7eac599dac822788b36064124e20112a844e9cd5156cb239
|
||||
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
||||
F src/fkey.c 928ed2517e8732113d2b9821aa37af639688d752f4ea9ac6e0e393d713eeb76f
|
||||
F src/func.c 1f61e32e7a357e615b5d2e774bee563761fce4f2fd97ecb0f72c33e62a2ada5f
|
||||
F src/func.c df400a1d3f4625997d4dd8a81951c303e066277c29b861d37e03cd152d7858dd
|
||||
F src/global.c 61a419dd9e993b9be0f91de4c4ccf322b053eb829868e089f0321dd669be3b90
|
||||
F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220
|
||||
F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
|
||||
@@ -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 74e383216a075251512d6ba98beb9dccd6da465e3f73817fc438379e3a628de7
|
||||
F test/percentile.test 9ae96346c6f5f000eeeca023cabf85efefd744f7b66031147354a4da6dcb50d6
|
||||
F test/permutations.test 405542f1d659942994a6b38a9e024cf5cfd23eaa68c806aeb24a72d7c9186e80
|
||||
F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f
|
||||
F test/pragma.test 11cb9310c42f921918f7f563e3c0b6e70f9f9c3a6a1cf12af8fccb6c574f3882
|
||||
@@ -2211,8 +2211,9 @@ F vsixtest/vsixtest.tcl 6195aba1f12a5e10efc2b8c0009532167be5e301abe5b31385638080
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P e48add02695a41b26a04e7942b5333e2bf4dc5598e363367aea3a4690982667d
|
||||
R 3b9cf08a862640c366f07bc714e510b5
|
||||
P ddc55efd2d59df3f20743b0533550436da945453c069025a3f871d28d40e13d4 f09904608195dac38172b0dd4dcab3190f33c116d468beff27f913a7433b400e
|
||||
R 55d55ff8a79d8904a0d256c79a96924e
|
||||
T +closed f09904608195dac38172b0dd4dcab3190f33c116d468beff27f913a7433b400e
|
||||
U drh
|
||||
Z ce5d41834ddc15c40aed5b812cb5e6fa
|
||||
Z debd110319d009b555173f7d44f9f322
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
ddc55efd2d59df3f20743b0533550436da945453c069025a3f871d28d40e13d4
|
||||
94cf96af8fee55449080655bddf81cbf5c078a02d7bb5dd7e4903b36f83a8c07
|
||||
|
||||
+6
-2
@@ -2049,7 +2049,11 @@ static void minMaxFinalize(sqlite3_context *context){
|
||||
** group_concat(EXPR, ?SEPARATOR?)
|
||||
** string_agg(EXPR, SEPARATOR)
|
||||
**
|
||||
** The SEPARATOR goes before the EXPR string. This is tragic. The
|
||||
** Content is accumulated in GroupConcatCtx.str with the SEPARATOR
|
||||
** coming before the EXPR value, except for the first entry which
|
||||
** omits the SEPARATOR.
|
||||
**
|
||||
** It is tragic that the SEPARATOR goes before the EXPR string. The
|
||||
** groupConcatInverse() implementation would have been easier if the
|
||||
** SEPARATOR were appended after EXPR. And the order is undocumented,
|
||||
** so we could change it, in theory. But the old behavior has been
|
||||
@@ -2153,7 +2157,7 @@ static void groupConcatInverse(
|
||||
/* pGCC is always non-NULL since groupConcatStep() will have always
|
||||
** run first to initialize it */
|
||||
if( ALWAYS(pGCC) ){
|
||||
int nVS;
|
||||
int nVS; /* Number of characters to remove */
|
||||
/* Must call sqlite3_value_text() to convert the argument into text prior
|
||||
** to invoking sqlite3_value_bytes(), in case the text encoding is UTF16 */
|
||||
(void)sqlite3_value_text(argv[0]);
|
||||
|
||||
@@ -229,4 +229,155 @@ ifcapable vtab {
|
||||
}
|
||||
}
|
||||
|
||||
# median() as a window function. (2024-08-31)
|
||||
#
|
||||
do_execsql_test percentile-3.0 {
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d);
|
||||
INSERT INTO t1 VALUES (1, 'A', 'one', 8.4),
|
||||
(2, 'B', 'two', 7.1),
|
||||
(3, 'C', 'three', 5.9),
|
||||
(4, 'D', 'one', 11.0),
|
||||
(5, 'E', 'two', 12.5),
|
||||
(6, 'F', 'three', 0.0),
|
||||
(7, 'G', 'one', 2.7);
|
||||
}
|
||||
do_execsql_test percentile-3.1 {
|
||||
SELECT a, b, c, d,
|
||||
group_concat(b,'.') OVER W1 AS 'elements',
|
||||
median(d) OVER W1 AS 'median'
|
||||
FROM t1
|
||||
WINDOW W1 AS (ORDER BY c, a ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
|
||||
} {
|
||||
1 A one 8.4 A.D 9.7
|
||||
4 D one 11.0 A.D.G 8.4
|
||||
7 G one 2.7 D.G.C 5.9
|
||||
3 C three 5.9 G.C.F 2.7
|
||||
6 F three 0.0 C.F.B 5.9
|
||||
2 B two 7.1 F.B.E 7.1
|
||||
5 E two 12.5 B.E 9.8
|
||||
}
|
||||
do_execsql_test percentile-3.2 {
|
||||
SELECT a, b, c, d,
|
||||
group_concat(b,'.') OVER W1 AS 'elements',
|
||||
median(d) OVER W1 AS 'median'
|
||||
FROM t1
|
||||
WINDOW W1 AS (ORDER BY c, a ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING)
|
||||
} {
|
||||
1 A one 8.4 A.D 9.7
|
||||
4 D one 11.0 A.D.G 8.4
|
||||
7 G one 2.7 A.D.G.C 7.15
|
||||
3 C three 5.9 A.D.G.C.F 5.9
|
||||
6 F three 0.0 A.D.G.C.F.B 6.5
|
||||
2 B two 7.1 A.D.G.C.F.B.E 7.1
|
||||
5 E two 12.5 A.D.G.C.F.B.E 7.1
|
||||
}
|
||||
do_execsql_test percentile-3.3 {
|
||||
SELECT a, b, c, d,
|
||||
group_concat(b,'.') OVER W1 AS 'elements',
|
||||
median(d) OVER W1 AS 'median'
|
||||
FROM t1
|
||||
WINDOW W1 AS (ORDER BY c, a ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING)
|
||||
} {
|
||||
1 A one 8.4 A.D.G.C.F.B.E 7.1
|
||||
4 D one 11.0 A.D.G.C.F.B.E 7.1
|
||||
7 G one 2.7 D.G.C.F.B.E 6.5
|
||||
3 C three 5.9 G.C.F.B.E 5.9
|
||||
6 F three 0.0 C.F.B.E 6.5
|
||||
2 B two 7.1 F.B.E 7.1
|
||||
5 E two 12.5 B.E 9.8
|
||||
}
|
||||
|
||||
# Test case adapted from examples shown at
|
||||
# https://database.guide/3-functions-to-calculate-the-median-in-sql/
|
||||
#
|
||||
do_execsql_test percential-4.0 {
|
||||
CREATE TABLE products(
|
||||
vendorId INT,
|
||||
productId INTEGER PRIMARY KEY,
|
||||
productName REAL,
|
||||
price REAL
|
||||
);
|
||||
INSERT INTO products VALUES
|
||||
(1001, 17, 'Left-handed screwdriver', 25.99),
|
||||
(1001, 49, 'Right-handed screwdriver', 25.99),
|
||||
(1001, 216, 'Long weight (blue)', 14.75),
|
||||
(1001, 31, 'Long weight (green)', 11.99),
|
||||
(1002, 37, 'Sledge hammer', 33.49),
|
||||
(1003, 7, 'Chainsaw', 245.00),
|
||||
(1003, 8, 'Straw dog box', 55.99),
|
||||
(1003, 12, 'Hammock', 11.01),
|
||||
(1004, 113, 'Teapot', 12.45),
|
||||
(1004, 117, 'Bottomless coffee mug', 9.99);
|
||||
}
|
||||
do_execsql_test percentile-4.1 {
|
||||
SELECT VendorId, ProductId, /* ProductName,*/ Price,
|
||||
avg(price) OVER (PARTITION BY vendorId) AS "Average",
|
||||
median(price) OVER (PARTITION BY vendorId) AS "Median"
|
||||
FROM products
|
||||
ORDER BY vendorId, productId;
|
||||
} {
|
||||
1001 17 25.99 19.68 20.37
|
||||
1001 31 11.99 19.68 20.37
|
||||
1001 49 25.99 19.68 20.37
|
||||
1001 216 14.75 19.68 20.37
|
||||
1002 37 33.49 33.49 33.49
|
||||
1003 7 245.0 104.0 55.99
|
||||
1003 8 55.99 104.0 55.99
|
||||
1003 12 11.01 104.0 55.99
|
||||
1004 113 12.45 11.22 11.22
|
||||
1004 117 9.99 11.22 11.22
|
||||
}
|
||||
do_execsql_test percentile-4.2 {
|
||||
SELECT vendorId, median(price) FROM products
|
||||
GROUP BY 1 ORDER BY 1;
|
||||
} {1001 20.37 1002 33.49 1003 55.99 1004 11.22}
|
||||
|
||||
do_execsql_test percentile-5.0 {
|
||||
CREATE TABLE user(name TEXT, class TEXT, cost REAL);
|
||||
INSERT INTO user VALUES
|
||||
('Alice', 'Y', 3578.27),
|
||||
('Bob', 'X', 3399.99),
|
||||
('Cindy', 'Z', 699.10),
|
||||
('Dave', 'Y', 3078.27),
|
||||
('Emma', 'Z', 2319.99),
|
||||
('Fred', 'Y', 539.99),
|
||||
('Gina', 'X', 2320.49),
|
||||
('Hank', 'W', 24.99),
|
||||
('Irma', 'W', 24.99),
|
||||
('Jake', 'X', 2234.99),
|
||||
('Kim', 'Y', 4319.99),
|
||||
('Liam', 'X', 4968.59),
|
||||
('Mia', 'W', 59.53),
|
||||
('Nate', 'W', 23.50);
|
||||
}
|
||||
do_execsql_test percentile-5.1 {
|
||||
SELECT name, class, cost,
|
||||
percentile_cont(cost, 0.00) OVER w1 AS 'P0',
|
||||
percentile_cont(cost, 0.25) OVER w1 AS 'P1',
|
||||
percentile_cont(cost, 0.50) OVER w1 AS 'P2',
|
||||
percentile_cont(cost, 0.75) OVER w1 AS 'P3',
|
||||
percentile_cont(cost, 1.00) OVER w1 AS 'P4'
|
||||
FROM user
|
||||
WINDOW w1 AS (PARTITION BY class)
|
||||
ORDER BY class, cost;
|
||||
} {
|
||||
Nate W 23.5 23.5 24.6175 24.99 33.625 59.53
|
||||
Hank W 24.99 23.5 24.6175 24.99 33.625 59.53
|
||||
Irma W 24.99 23.5 24.6175 24.99 33.625 59.53
|
||||
Mia W 59.53 23.5 24.6175 24.99 33.625 59.53
|
||||
Jake X 2234.99 2234.99 2299.115 2860.24 3792.14 4968.59
|
||||
Gina X 2320.49 2234.99 2299.115 2860.24 3792.14 4968.59
|
||||
Bob X 3399.99 2234.99 2299.115 2860.24 3792.14 4968.59
|
||||
Liam X 4968.59 2234.99 2299.115 2860.24 3792.14 4968.59
|
||||
Fred Y 539.99 539.99 2443.7 3328.27 3763.7 4319.99
|
||||
Dave Y 3078.27 539.99 2443.7 3328.27 3763.7 4319.99
|
||||
Alice Y 3578.27 539.99 2443.7 3328.27 3763.7 4319.99
|
||||
Kim Y 4319.99 539.99 2443.7 3328.27 3763.7 4319.99
|
||||
Cindy Z 699.1 699.1 1104.3225 1509.545 1914.7675 2319.99
|
||||
Emma Z 2319.99 699.1 1104.3225 1509.545 1914.7675 2319.99
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
Reference in New Issue
Block a user