Code: Select all
CompilerIf #PB_Compiler_IsMainFile
EnableExplicit
CompilerEndIf
ImportC ""
qsort(*base, nitems.l, size.l, *Prog)
;qsort(void *base, size_t nitems, size_t size, Int (*compar)(const void *, const void*))
EndImport
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_MacOS
ImportC "/Applications/PureBasic/purelibraries/macos/libraries/libpbsqlite3.a"
CompilerDefault
ImportC "sqlite3.lib"
CompilerEndSelect
sqlite3_create_function(DatabaseID, zFunctionName.p-utf8, nArg.l, eTextRep.l, *pApp, *xFunc, *xStep, *xFinal)
sqlite3_aggregate_context(*sqlite3_context, nBytes.l)
sqlite3_result_double(*sqlite3_context, dbl.d)
sqlite3_value_numeric_type.l(*sqlite3_value)
sqlite3_value_double.d(*sqlite3_value)
sqlite3_value_type.i(*sqlite3_value)
sqlite3_realloc(*void, int.l)
sqlite3_free(*void)
sqlite3_result_error(*sqlite3_context, char.p-utf8, int.l)
sqlite3_result_error_nomem(*sqlite3_context)
sqlite3_errcode.i(*db)
sqlite3_errmsg16.i(*sqlite3)
EndImport
#SQLITE_OK = 0
#SQLITE_UTF8 = 1
#SQLITE_INTEGER = 1
#SQLITE_FLOAT = 2
#SQLITE3_TEXT = 3
#SQLITE_BLOB = 4
#SQLITE_NULL = 5
Structure Percentile
nAlloc.i ; Number of slots allocated for a[]
nUsed.i ; Number of slots actually used in a[]
rPct.d ; 1.0 more than the value for P
*a.Double ; Array of Y values
EndStructure
Procedure.i sameValue(a.d, b.d)
a - b
ProcedureReturn Bool(a >= -0.001 And a <= 0.001)
EndProcedure
ProcedureC percentStep(*pCtx, argc.l, *argv)
Protected *ptr, *a
Protected *p.Percentile
Protected.d rPct
Protected.i eType
Protected.d y
Protected.i n
; Requirement 3: P must be a number between 0 And 100
*ptr = PeekI(*argv + 1 * SizeOf(Long))
eType = sqlite3_value_numeric_type(*ptr)
rPct = sqlite3_value_double(*ptr);
If (eType <> #SQLITE_INTEGER And eType <> #SQLITE_FLOAT) Or (rPct < 0.0 Or rPct > 100.0)
sqlite3_result_error(*pCtx, "2nd argument to percentile() is not a number between 0.0 And 100.0", -1)
ProcedureReturn
EndIf
; Allocate the session context.
*p = sqlite3_aggregate_context(*pCtx, SizeOf(Percentile))
If *p = 0
ProcedureReturn
EndIf
; 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
ElseIf Not sameValue(*p\rPct, rPct); + 1.0)
sqlite3_result_error(*pCtx, "2nd argument to percentile() is not the same For all input rows", -1)
ProcedureReturn
EndIf
; Ignore rows For which Y is NULL
*ptr = PeekI(*argv + 0 * SizeOf(Long))
eType = sqlite3_value_type(*ptr)
If eType = #SQLITE_NULL
ProcedureReturn
EndIf
; If Not NULL, then Y must be numeric. Otherwise throw an error.
; Requirement 4
If eType <> #SQLITE_INTEGER And eType <> #SQLITE_FLOAT
sqlite3_result_error(*pCtx, "1st argument to percentile() is not numeric", -1)
ProcedureReturn
EndIf
; Throw an error If the Y value is infinity Or NaN
y = sqlite3_value_double(*ptr)
If IsInfinity(y)
sqlite3_result_error(*pCtx, "Inf input to percentile()", -1)
ProcedureReturn
EndIf
; Allocate And store the Y
If *p\nUsed >= *p\nAlloc
n = *p\nAlloc * 2 + 250
*a = sqlite3_realloc(*p\a, 8 * n)
If *a = 0
sqlite3_free(*p\a)
ClearStructure(*p, Percentile)
sqlite3_result_error_nomem(*pCtx)
ProcedureReturn
EndIf
*p\nAlloc = n
*p\a = *a
EndIf
;*p\a[*p\nUsed] = y
PokeD(*p\a + 8 * *p\nUsed, y)
*p\nUsed + 1
EndProcedure
ProcedureC.l doubleCmp(*pA.Double, *pB.Double)
Protected Result.l
Result = 1
If *pA\d = *pB\d
Result = 0
ElseIf *pA\d < *pB\d
Result = -1
EndIf
ProcedureReturn Result
EndProcedure
ProcedureC percentFinal(*pCtx)
Protected *p.Percentile
Protected.i i1, i2
Protected.d v1, v2
Protected.d ix, vx
Protected.i i
Protected.d v11, v22
*p = sqlite3_aggregate_context(*pCtx, 0)
If *p
If *p\a <> 0
If *p\nUsed
If *p\rPct > 0
If *p\rPct = 100
vx = PeekD(*p\a + 8 * (*p\nUsed - 1))
Else
qsort(*p\a, *p\nUsed, SizeOf(double), @doubleCmp())
; Debug "-----"
; For i = 0 To *p\nUsed - 1
; Debug Str(i) + ": " + StrD(PeekD(*p\a + 8 * i))
; Next i
; Debug "+++++"
i1 = Round(*p\rPct / 100 * *p\nUsed, #PB_Round_Up) - 1
vx = PeekD(*p\a + 8 * i1)
EndIf
Else
vx = 0
EndIf
sqlite3_result_double(*pCtx, vx)
EndIf
sqlite3_free(*p\a)
EndIf
ClearStructure(*p, Percentile)
EndIf
EndProcedure
Procedure.i sqlite3_percentile_init(db.i)
ProcedureReturn Bool(sqlite3_create_function(DatabaseID(db), "percentile", 2, #SQLITE_UTF8, 0, 0, @percentStep(), @percentFinal()) = #SQLITE_OK)
EndProcedure
CompilerIf #PB_Compiler_IsMainFile
Define.i DB, i, j
UseSQLiteDatabase()
DB = OpenDatabase(#PB_Any, ":memory:", "", "")
If DB
If sqlite3_percentile_init(DB)
DatabaseUpdate(DB, "CREATE TABLE test(a INTEGER)")
DatabaseUpdate(DB, "INSERT INTO test VALUES (15)")
DatabaseUpdate(DB, "INSERT INTO test VALUES (20)")
DatabaseUpdate(DB, "INSERT INTO test VALUES (35)")
DatabaseUpdate(DB, "INSERT INTO test VALUES (40)")
DatabaseUpdate(DB, "INSERT INTO test VALUES (50)")
If DatabaseQuery(DB, "SELECT percentile(a, 50) FROM test")
While NextDatabaseRow(DB)
Debug "Res: " + GetDatabaseString(DB, 0)
Wend
FinishDatabaseQuery(DB)
If sqlite3_errcode(DatabaseID(DB))
Debug PeekS(sqlite3_errmsg16(DatabaseID(DB)))
EndIf
Else
Debug DatabaseError()
EndIf
EndIf
CloseDatabase(DB)
EndIf
CompilerEndIf