Escribiendo una función de agregación definida por el usuario en IRIS - ejemplo: Mediana
Las funciones de agregación definidas por el usuario han sido compatibles con IRIS desde la versión 2021.1.0. Hace años deseaba tener esta funcionalidad antes de encontrar una forma alternativa y secreta de sobrescribir MAX y MIN en un tipo de dato personalizado, pero no tuve la oportunidad de probarlo realmente hasta hoy. Me pareció una experiencia/ejemplo interesante – ya antes surgió la pregunta de cómo obtener la Mediana en IRIS SQL – así que lo comparto aquí sin extenderme demasiado.
Una advertencia: las UDAFs no tienen la misma paridad entre objeto y SQL que otros tipos de funciones, por lo que realmente necesitáis ejecutar SQL para definir la función de agregación (envuelta útilmente en un método de clase en el ejemplo de abajo). Compilar solo la clase no es suficiente.
/// Class implementing a Median aggregate function for IRIS SQLClass DC.Demo.Median
{
/// Returns a new global ref in IRISTEMP to use to store intermediate resultsClassMethod Initialize() As%String [ PublicList = ref, SqlProc ]
{
New ref
Set ref = $Name(^IRIS.Temp.UDAF.Median($Increment(^IRIS.Temp.UDAF.Median)))
Set @ref = 0Quit ref
}
/// Updates temp global for a single recordClassMethod Iterate(ref As%String, value As%Numeric) As%String [ SqlProc ]
{
If (value '= "") {
Do$Increment(@ref)
Do$Increment(@ref@(+value))
}
Quit ref
}
/// Finds the actual median (possibly an average of the two middle values)ClassMethod Finalize(ref As%String) As%Numeric [ SqlProc ]
{
Set median = ""Set total = @ref
Set position1 = (total+1)\2Set position2 = (total+2)\2Set val1 = ""Set val2 = ""Set reached = 0Set key = ""For {
Set key = $Order(@ref@(key),1,refCount)
Quit:key=""set reached = reached + refCount
if (reached >= position1) && (val1 = "") {
Set val1 = key
}
if (reached >= position2) && (val2 = "") {
Set val2 = key
}
If (val1 '= "") && (val2 '= "") {
Set median = (val1+val2)/2Quit
}
}
Kill @ref
Quit median
}
/// To actually define the UDAF from an SQL perspective, call this classmethod.ClassMethod Define()
{
// Drop the function in case something has changed
&sql(DROPAGGREGATE DC_Demo.Median)
&sql(CREATEAGGREGATE DC_Demo.Median(arg NUMERIC) RETURNS NUMERIC
INITIALIZE WITH DC_Demo.Median_Initialize
ITERATE WITH DC_Demo.Median_Iterate
FINALIZE WITH DC_Demo.Median_Finalize)
$$$ThrowSQLIfError(SQLCODE,%msg)
}
}
¡Espero que esto ayude a alguien!