Allow percentile() and median() to act as window functions.

FossilOrigin-Name: 94cf96af8fee55449080655bddf81cbf5c078a02d7bb5dd7e4903b36f83a8c07
This commit is contained in:
drh
2024-08-31 18:08:31 +00:00
8 changed files with 355 additions and 36 deletions
+2 -1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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;
}
+1
View File
@@ -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 \
+12 -11
View File
@@ -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
View File
@@ -1 +1 @@
ddc55efd2d59df3f20743b0533550436da945453c069025a3f871d28d40e13d4
94cf96af8fee55449080655bddf81cbf5c078a02d7bb5dd7e4903b36f83a8c07
+6 -2
View File
@@ -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]);
+151
View File
@@ -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