Particionado artesanal
IRIS 2026.1 proporcionó tablas particionadas como una nueva opción para grandes conjuntos de datos. Es una gran mejora, ya que ofrece estandarización de esta funcionalidad.
Sin embargo: antes también era posible hacerlo, cumpliendo los requisitos y dejando espacio para la creatividad. Era menos elegante, con un poco más de código y menos automatismos.
La necesidad es obvia.
Un ejemplo:
Tenéis una tabla PERSON que contiene Clientes, Empleados, Socios, Proveedores, ....
El enfoque clásico sería indexar los distintos roles o heredar clases individuales a partir de una clase común.
El impacto inmediato:
Todos los registros acaban en los mismos 3 Globals para datos, índices y streams.
Esto podría significar que buscar uno de 37 empleados tarde, de media, lo mismo que buscar uno entre 277.876 clientes o entre 1.612 proveedores.
Simplificado: son habitantes del mismo estanque de ranas.
Separar su almacenamiento en Globals distintos podría prometer una mejora.
El enfoque más simple podría ser usar 4 tablas con nombres diferentes, con todo el impacto asociado en la nomenclatura de las herramientas de mantenimiento. Muchas implementaciones en el campo funcionan así. @Iryna Mykhailova lo describe en su artículo Almacenamiento de datos para clases y sus superclases por separado.
Existe un enfoque más sofisticado
Desde hace tiempo, estos 2 parámetros de clase permiten una solución bastante dinámica.
- El parámetro DEFAULTGLOBAL se utiliza como raíz global por defecto para los valores de las palabras clave de almacenamiento DATALOCATION, IDLOCATION, INDEXLOCATION y STREAMLOCATION.
- Por ejemplo, si DEFAULTGLOBAL = "^Demo.Person"
- esto se compila como
- DATALOCATION = ^Demo.PersonD
- IDLOCATION = ^Demo.PersonD
- INDEXLOCATION = ^Demo.PersonI
- STREAMLOCATION = ^Demo.PersonS
- El parámetro MANAGEDEXTENT puede establecerse en 0 (cero) para hacer que el gestor de extensiones ignore esta clase. Si se establece en 1, el gestor de extensiones registrará los globals utilizados por la clase y detectará colisiones.
Ahora podemos usar DEFAULTGLOBAL en combinación con indirectio
- Parámetro DEFAULTGLOBAL As STRING = "@%rcc";
- Asignación de Globals por indirección en tiempo de ejecución.
- compilado como:
- <DataLocation>@%rccD</DataLocation>
- <DefaultData>RCCDefaultData</DefaultData>
- <IdLocation>@%rccD</IdLocation>
- <IndexLocation>@%rccI</IndexLocation>
- <StreamLocation>@%rccS</StreamLocation>
- Parámetro MANAGEDEXTENT As INTEGER = 0;
- permite un cambio dinámico de los Globals de almacenamiento
- y protege contra errores durante la compilación de la clase.
Solo tenéis que proporcionar los nombres correctos para estas 3 variables antes de su uso:
- set %rccD = "^"%rcc"D"
- set %rccI = "^"%rcc"I"
- set %rccS = "^"%rcc"S"
Empaquetarlo en un método de clase pequeño no es gran cosa.
Proyectarlo como SqlProcedure también lo hace disponible para cualquier acceso SQL.
El ejemplo para el Global ^Person*:
SAMPLES>write ##class(RCC).%rcc("Person")
1
SAMPLES>zwrite
%rccD="^PersonD"
%rccI="^PersonI"
%rccS="^PersonS"
%sqlcontext=<OBJECT REFERENCE>[2@%Library.ProcedureContext]
SAMPLES>write ##class(RCC).Populate(11)
11
SAMPLES>:sql
SQL Command Line Shell
----------------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]SAMPLES>>select * from RCC where %rcc('Person')=1
1. select * from RCC where %rcc('Person')=1
| ID | city | dob | name | score |
| -- | -- | -- | -- | -- |
| 1 | Vail | 40558 | Leiberman,Sally E. | 97 |
| 2 | Chicago | 58285 | Ott,Lydia H. | 44 |
| 3 | Pueblo | 58873 | Hertz,Usha L. | 45 |
| 4 | Gansevoort | 40744 | Gomez,Heloisa O. | 53 |
| 5 | Ukiah | 34822 | Macrakis,Keith S. | 39 |
| 6 | Ukiah | 31655 | Gore,Chris N. | 9 |
| 7 | Hialeah | 39868 | Grabscheid,Amanda O. | 66 |
| 8 | Vail | 60410 | Burroughs,Greta W. | 88 |
| 9 | Tampa | 43499 | Zevon,Al W. | 15 |
| 10 | Tampa | 59787 | Feynman,Juanita Q. | 93 |
| 11 | Miami | 53081 | Zucherro,Bart R. | 42 |
11 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0774s/44,458/226,219/0ms
execute time(s)/globals/cmds/disk: 0.0004s/12/1,938/0ms
query class: %sqlcq.SAMPLES.cls1
---------------------------------------------------------------------------
[SQL]SAMPLES>> -Misma sesión con el Global ^mtemp.Work*:
SAMPLES>write ##class(RCC).%rcc("mtemp.Work")
1
SAMPLES>zw
%rccD="^mtemp.WorkD"
%rccI="^mtemp.WorkI"
%rccS="^mtemp.WorkS"
%sqlcontext=<OBJECT REFERENCE>[2@%Library.ProcedureContext]
SAMPLES>w ##class(RCC).Populate(5)
5
SAMPLES>:sql
SQL Command Line Shell
----------------------------------------------------
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]SAMPLES>>select name,city from RCC where %rcc('mtemp.Work')=1 order by city
2. select name,city from RCC where %rcc('mtemp.Work')=1 order by city
| name | city |
| -- | -- |
| Gaboriault,Quentin O. | Bensonhurst |
| Baker,Andrew Q. | Fargo |
| Zweifelhofer,Clint T. | Miami |
| Fives,Patrick T. | Newton |
| Alton,Geoffrey I. | Pueblo |
5 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0596s/38,492/210,709/0ms
execute time(s)/globals/cmds/disk: 0.0002s/6/919/0ms
query class: %sqlcq.SAMPLES.cls5
---------------------------------------------------------------------------
[SQL]SAMPLES>>Y la definición de la clase:
Class User.RCC Extends (%Persistent, %Populate) [ Final ]
{
Parameter MANAGEDEXTENT As INTEGER = 0;
Parameter DEFAULTGLOBAL As STRING = "@%rcc";
Property name As %String(POPSPEC = "Name()", TRUNCATE = 1);
Property city As %String(POPSPEC = "City()", TRUNCATE = 1);
Property dob As %Date;
Property score As %Integer(POPSPEC = "Integer(0,100)");
//
Index nameIDX On name [ Data = city ];
Index cityIDX On city As SQLSTRING [ Type = index ];
Index nsIDX On (name, score);
Index ncIDX On (name, city);
Index dobIDX On dob [ Data = name ];
//
ClassMethod %rcc(%rcc = "") As %Integer [ SqlName = %rcc, SqlProc ]
{
if '$l(%rcc) set %rcc="mtemp."_$job
set %rccD="^"_%rcc_"D"
set %rccI="^"_%rcc_"I"
set %rccS="^"_%rcc_"S"
quit $$$OK
}
//
Storage Default
{
<Data name="RCCDefaultData">
<Value name="1">
<Value>name</Value>
</Value>
<Value name="2">
<Value>city</Value>
</Value>
<Value name="3">
<Value>dob</Value>
</Value>
<Value name="4">
<Value>score</Value>
</Value>
</Data>
<DataLocation>@%rccD</DataLocation>
<DefaultData>RCCDefaultData</DefaultData>
<IdLocation>@%rccD</IdLocation>
<IndexLocation>@%rccI</IndexLocation>
<StreamLocation>@%rccS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
}
Resumen:
Todo lo que necesitáis para usar este enfoque en ISOS:
;; estableced vuestros globals de acceso antes de usarlo
do ##class(RCC).%rcc("your.globalname")
Para SQL usad la condición WHERE estática correspondiente:
SELECT ........ from RCC WHERE %rcc('your.globalname')=1 ....
Y esto funciona para cualquier referencia válida a un Global,
incluyendo globals privados de proceso, globals temporales, referencias extendidas, etc.
Si no proporcionáis nada, se utiliza "mtemp."_$job