T1.2: Creates Properties and Indexes

Knowledge Review - InterSystems ObjectScript Specialist

1. Establece la longitud máxima para propiedades de cadena (parámetro MAXLEN, TRUNCATE)

Puntos Clave

  • El parámetro MAXLEN controla la longitud máxima de una propiedad %String (por defecto son 50 caracteres)
  • Establecer `MAXLEN = ""` fija el límite en la longitud máxima de cadena de IRIS (~3.6 millones de caracteres)
  • El parámetro TRUNCATE determina si los valores que exceden MAXLEN se truncan silenciosamente o se rechazan
  • MAXLEN afecta las definiciones de columnas SQL (tamaño VARCHAR)
  • Siempre establecer MAXLEN explícitamente cuando el valor por defecto de 50 es insuficiente

Notas Detalladas

Descripción general

Las propiedades de cadena en InterSystems IRIS tienen una longitud máxima por defecto de 50 caracteres, lo cual es a menudo insuficiente para datos del mundo real. El parámetro MAXLEN controla este límite y afecta directamente tanto la validación de objetos como la definición de la columna SQL.

Configuración de MAXLEN

Class Sample.Patient Extends %Persistent
{
    /// Name with explicit max length
    Property Name As %String(MAXLEN = 200);

    /// Description with maximum IRIS string length (~3.6 MB)
    Property Notes As %String(MAXLEN = "");

    /// Uses the default MAXLEN of 50
    Property Code As %String;
}

En SQL, MAXLEN se mapea al tamaño VARCHAR:

  • Property Name As %String(MAXLEN = 200) se convierte en VARCHAR(200)
  • Property Notes As %String(MAXLEN = "") se convierte en VARCHAR(MAXVAL) (longitud máxima de cadena de IRIS, ~3.6 millones de caracteres)

El Parámetro TRUNCATE

El parámetro TRUNCATE controla qué sucede cuando un valor excede MAXLEN:

  • TRUNCATE = 0 (por defecto): El valor se rechaza y %Save() devuelve un estado de error
  • TRUNCATE = 1: El valor se trunca silenciosamente a MAXLEN caracteres
Property ShortCode As %String(MAXLEN = 5, TRUNCATE = 1);

Con TRUNCATE = 1, establecer ShortCode como "ABCDEFGH" almacenaría silenciosamente "ABCDE".

Consideraciones Prácticas

  • Siempre verificar que MAXLEN sea suficiente para datos de producción; el valor por defecto de 50 es una fuente común de errores de truncamiento de datos
  • Para propiedades que almacenan texto de longitud variable (descripciones, notas, direcciones), considerar usar MAXLEN = ""
  • Cuando los datos pueden exceder la longitud máxima de cadena de IRIS (~3.6 millones de caracteres / ~3.6 MB) — como PDFs grandes, imágenes o documentos — usar una propiedad de tipo stream en su lugar
  • Cambiar MAXLEN en una propiedad existente puede requerir reconstruir los índices que incluyen esa propiedad

Referencias de Documentación

2. Usa propiedades de tipo stream para conjuntos de datos grandes (%Stream.GlobalCharacter, %Stream.FileBinary)

Puntos Clave

  • Los streams manejan datos demasiado grandes para propiedades de cadena estándar (documentos, imágenes, archivos)
  • %Stream.GlobalCharacter: Datos de caracteres almacenados en globals (lo más común para texto); **comprimidos automáticamente** por IRIS
  • %Stream.GlobalBinary: Datos binarios almacenados en globals (imágenes, PDFs almacenados en la base de datos); **comprimidos automáticamente** por IRIS
  • %Stream.FileCharacter: Datos de caracteres almacenados en archivos externos en el sistema de archivos (no comprimidos por defecto, pero existen subclases comprimidas)
  • %Stream.FileBinary: Datos binarios almacenados en archivos externos en el sistema de archivos (no comprimidos por defecto, pero existen subclases comprimidas como `%Stream.FileBinaryGzip`)
  • Los streams basados en globals se comprimen automáticamente — esta es la opción recomendada cuando se desea compresión de IRIS
  • Los streams usan los métodos `Write()`, `Read()`, `Rewind()` y `MoveToEnd()` en lugar de asignación directa
  • `WriteLine()` solo está disponible en streams de caracteres — agrega datos seguidos de un terminador de línea

Notas Detalladas

Descripción general

Cuando los datos exceden los límites prácticos de una propiedad de cadena (o al manejar contenido binario como imágenes o documentos), las propiedades de tipo stream proporcionan un mecanismo de almacenamiento por fragmentos. Los streams se leen y escriben secuencialmente, lo que los hace adecuados para datos grandes que no necesitan acceso aleatorio.

Tipos de Streams

Clase de StreamTipo de ContenidoUbicación de AlmacenamientoComprimido por IRIS
%Stream.GlobalCharacterTexto (caracteres)Globals de la base de datos
%Stream.GlobalBinaryBinarioGlobals de la base de datos
%Stream.FileCharacterTexto (caracteres)Sistema de archivos externoNo (por defecto)
%Stream.FileBinaryBinarioSistema de archivos externoNo (por defecto)

Declaración de Propiedades de Tipo Stream

Class Sample.Document Extends %Persistent
{
    Property Title As %String(MAXLEN = 200);

    /// Large text content stored in globals
    Property Content As %Stream.GlobalCharacter;

    /// PDF file stored in the database
    Property Attachment As %Stream.GlobalBinary;
}

Escritura en Streams

Set doc = ##class(Sample.Document).%New()
Set doc.Title = "My Report"

// Write text to the character stream
Do doc.Content.Write("This is the first paragraph of the report. ")
Do doc.Content.Write("This is the second paragraph.")
Do doc.Content.WriteLine("")  // adds a line with newline

// Save the object (stream data is saved with the object)
Set sc = doc.%Save()

Lectura desde Streams

Set doc = ##class(Sample.Document).%OpenId(1)

// Rewind to the beginning
Do doc.Content.Rewind()

// Read in chunks
While 'doc.Content.AtEnd {
    Set chunk = doc.Content.Read(32000)
    Write chunk
}

Métodos Clave de Streams

  • Write(data): Agrega datos al stream (todos los tipos de stream)
  • WriteLine(data): Agrega datos seguidos de un terminador de línea (solo streams de caracteres)
  • Read(len): Lee hasta len caracteres/bytes desde la posición actual
  • ReadLine(): Lee una línea de texto
  • Rewind(): Reinicia la posición de lectura al principio
  • MoveToEnd(): Se posiciona al final para agregar datos
  • Clear(): Elimina todo el contenido del stream
  • Size: Propiedad que devuelve el tamaño total del stream
  • AtEnd: Propiedad que indica si la posición de lectura está al final

Streams Basados en Archivos

// Write to an external file
Set fileStream = ##class(%Stream.FileCharacter).%New()
Set fileStream.Filename = "/tmp/output.txt"
Do fileStream.Write("Content for external file")
Set sc = fileStream.%Save()

Copia entre Streams

// Copy one stream to another
Do targetStream.CopyFrom(sourceStream)

Compresión de Streams

Los streams basados en globals (%Stream.GlobalCharacter y %Stream.GlobalBinary) se comprimen automáticamente por IRIS cuando se almacenan en la base de datos. Esta compresión es transparente — los datos se comprimen al escribir y se descomprimen al leer sin código adicional. Esto hace que los streams basados en globals sean la opción recomendada cuando desea que IRIS maneje la compresión automáticamente.

Los streams basados en archivos (%Stream.FileCharacter y %Stream.FileBinary) se almacenan como archivos regulares en el sistema de archivos del sistema operativo y no se comprimen por defecto. Sin embargo, IRIS proporciona subclases comprimidas como %Stream.FileBinaryGzip que almacenan el archivo en formato gzip.

Consejo para el examen: Cuando una pregunta pide almacenar datos grandes que "deben comprimirse automáticamente por IRIS si es posible", elija una clase de stream basada en globals (%Stream.GlobalCharacter para texto, %Stream.GlobalBinary para datos binarios).

Referencias de Documentación

3. Crea propiedades que calculan valores dinámicamente o se actualizan automáticamente (Calculated, SqlComputed, SqlComputeOnChange)

Puntos Clave

  • Las propiedades Calculated no tienen almacenamiento; su valor se computa cada vez mediante un método Get personalizado
  • Las propiedades SqlComputed tienen una expresión de código SQL que proporciona el valor en contexto SQL
  • SqlComputeOnChange especifica qué propiedades disparan el recálculo en INSERT o UPDATE
  • Las propiedades Calculated sin SqlComputed aparecerán como NULL en las consultas SQL
  • Las propiedades Transient existen solo en memoria (no se almacenan) pero se diferencian de Calculated en que se pueden asignar

Notas Detalladas

Descripción general

InterSystems IRIS proporciona varios mecanismos para propiedades cuyos valores se derivan de otros datos en lugar de almacenarse independientemente. Elegir el mecanismo correcto depende de si el valor necesita estar disponible en SQL, si debe almacenarse para indexación y cuándo debe recalcularse.

Propiedades Calculated

Una propiedad Calculated no tiene almacenamiento. Cada vez que se accede a la propiedad, el sistema llama al método Get de la propiedad para computar el valor.

Class Sample.Person Extends %Persistent
{
    Property FirstName As %String;
    Property LastName As %String;

    /// Full name computed on access
    Property FullName As %String [ Calculated, SqlComputeCode = {
        Set {*} = {FirstName} _ " " _ {LastName}
    }, SqlComputed ];

    Method FullNameGet() As %String
    {
        Return ..FirstName _ " " _ ..LastName
    }
}

Características clave:

  • No se asigna almacenamiento para la propiedad
  • El método Get se llama cada vez que se accede a la propiedad en ObjectScript
  • No se puede asignar directamente (no tiene método Set)
  • Debe especificarse también SqlComputed y SqlComputeCode si el valor debe ser visible en SQL

Propiedades SqlComputed

SqlComputed proporciona un valor en el contexto SQL. Combinado con almacenamiento, el valor puede indexarse.

Property AgeGroup As %String [ SqlComputeCode = {
    Set {*} = $Select({Age}<18:"Minor", {Age}<65:"Adult", 1:"Senior")
}, SqlComputed, SqlComputeOnChange = Age ];

En el SqlComputeCode:

  • {*} se refiere a la propiedad que se está computando
  • {PropertyName} se refiere a otras propiedades en la misma clase
  • {%%INSERT} es verdadero durante operaciones INSERT
  • {%%UPDATE} es verdadero durante operaciones UPDATE

SqlComputeOnChange

Esta palabra clave especifica qué cambios de propiedad deben disparar el recálculo:

Property TotalPrice As %Numeric [ SqlComputeCode = {
    Set {*} = {Quantity} * {UnitPrice}
}, SqlComputed, SqlComputeOnChange = (Quantity, UnitPrice) ];
  • Cuando Quantity o UnitPrice cambian (vía INSERT o UPDATE), TotalPrice se recomputa automáticamente
  • Especificar %%INSERT para computar solo en registros nuevos
  • Especificar múltiples propiedades disparadoras entre paréntesis
  • Sin SqlComputeOnChange, el valor se computa en cada INSERT y UPDATE

Computada Almacenada vs Calculada

CaracterísticaCalculated + SqlComputedSqlComputed (sin Calculated)
Valor almacenado en discoNo
Puede indexarseNo
Visibilidad SQLComputada en cada consultaAlmacenada, recuperación rápida
Acceso desde objetoVía método GetDirectamente desde almacenamiento

Propiedades Transient

Las propiedades Transient solo existen en memoria (no se almacenan) pero a diferencia de las propiedades Calculated, se pueden asignar:

Property TempResult As %String [ Transient ];

Referencias de Documentación

4. Crea y valida parámetros y atributos de miembros de clase (Required, InitialExpression, palabras clave de parámetros)

Puntos Clave

  • La palabra clave Required asegura que una propiedad debe tener un valor antes de que %Save() tenga éxito
  • InitialExpression establece un valor por defecto cuando %New() crea un objeto
  • Los parámetros de propiedad (MAXLEN, MINVAL, MAXVAL, VALUELIST, etc.) restringen los valores permitidos
  • Los parámetros de clase definen constantes accesibles en toda la clase vía `..#ParamName`
  • La validación ocurre durante %Save() y respeta todas las restricciones

Notas Detalladas

Descripción general

Los miembros de clase en InterSystems IRIS pueden configurarse con varias palabras clave y parámetros que controlan la validación, los valores por defecto y el comportamiento. Estos atributos son esenciales para construir modelos de datos robustos que apliquen reglas de negocio a nivel de objeto.

La Palabra Clave Required

Cuando una propiedad se marca como Required, %Save() fallará si la propiedad no tiene valor:

Class Sample.Order Extends %Persistent
{
    Property OrderNumber As %String [ Required ];
    Property CustomerName As %String [ Required ];
    Property OrderDate As %Date [ InitialExpression = {+$HOROLOG} ];
    Property Status As %String [ InitialExpression = "NEW" ];
}

Intentar guardar sin establecer OrderNumber o CustomerName devuelve un error %Status.

InitialExpression

InitialExpression proporciona un valor por defecto asignado cuando %New() crea una instancia:

Property CreatedDate As %Date [ InitialExpression = {+$HOROLOG} ];
Property Active As %Boolean [ InitialExpression = 1 ];
Property Counter As %Integer [ InitialExpression = 0 ];

Nota: Las expresiones dentro de { } se evalúan en tiempo de ejecución. Sin llaves, el valor es una constante literal.

Parámetros de Propiedad Comunes

Class Sample.Product Extends %Persistent
{
    /// String with constrained length
    Property Name As %String(MAXLEN = 100, MINLEN = 1);

    /// Numeric with range constraints
    Property Price As %Numeric(MAXVAL = 99999.99, MINVAL = 0, SCALE = 2);

    /// Enumerated values
    Property Category As %String(VALUELIST = ",Electronics,Clothing,Food,Other");

    /// Display values for the enumeration
    Property Priority As %String(
        VALUELIST = ",H,M,L",
        DISPLAYLIST = ",High,Medium,Low"
    );

    /// Pattern-matched string
    Property ZipCode As %String(PATTERN = "5N.1(1""-""4N)");
}

Parámetros de Clase

Los parámetros de clase definen constantes para toda la clase:

Class Sample.Config Extends %Persistent
{
    Parameter DEFAULTCATEGORY = "General";
    Parameter MAXRETRIES As %Integer = 3;
    Parameter TABLENAME = "Products";

    Method GetCategory() As %String
    {
        Return ..#DEFAULTCATEGORY
    }
}

Los parámetros pueden sobrescribirse en subclases:

Class Sample.SpecialConfig Extends Sample.Config
{
    Parameter DEFAULTCATEGORY = "Special";
}

Comportamiento de Validación

La validación ocurre en varios puntos: 1. Asignación de propiedad: La verificación de tipo ocurre cuando se asigna un valor a una propiedad 2. %Save(): Verificaciones de Required, validación de VALUELIST, verificaciones de MAXLEN/MINLEN 3. %ValidateObject(): Se puede llamar explícitamente para validar sin guardar 4. SQL INSERT/UPDATE: La misma validación se aplica a través de operaciones SQL

Set order = ##class(Sample.Order).%New()
// OrderNumber is Required but not set
Set sc = order.%Save()
// sc contains an error - Required field missing
If $$$ISERR(sc) {
    Do $System.Status.DisplayError(sc)
    // Displays: "ERROR #7201: Property 'OrderNumber' required"
}

Referencias de Documentación

5. Selecciona el tipo de índice apropiado según la distribución de datos (estándar, bitmap, bitslice)

Puntos Clave

  • Índice estándar (B-tree): Mejor para datos de alta cardinalidad (muchos valores únicos); soporta consultas de igualdad y rango
  • Índice bitmap: Mejor para datos de baja cardinalidad (pocos valores distintos); excelente para consultas complejas con AND/OR
  • Índice bitslice: Especializado para operaciones de agregación (SUM, COUNT, AVG) en datos numéricos
  • La selección del tipo de índice depende de la distribución de datos, los patrones de consulta y la frecuencia de actualización
  • Los índices bitmap requieren un ID entero positivo (asignado por el sistema, no personalizado)

Notas Detalladas

Descripción general

Los índices mejoran drásticamente el rendimiento de las consultas, pero elegir el tipo de índice incorrecto puede desperdiciar almacenamiento y ralentizar las operaciones de escritura. Comprender las características de cada tipo de índice y hacerlos coincidir con los patrones de datos es crucial.

Índices Estándar (B-tree)

Los índices estándar organizan los datos en una estructura de árbol balanceado, haciéndolos eficientes para:

  • Búsquedas de igualdad (WHERE Name = 'Smith')
  • Consultas de rango (WHERE Age > 30 AND Age < 50)
  • Operaciones de ordenamiento (ORDER BY)
  • Columnas de alta cardinalidad (muchos valores únicos como nombres, fechas, IDs)
Index NameIdx On Name;
Index DOBIdx On DOB;
Index CompoundIdx On (LastName, FirstName);

Índices Bitmap

Los índices bitmap usan cadenas de bits donde cada bit representa una fila. Son extremadamente eficientes para:

  • Columnas de baja cardinalidad (género, estado, categoría, códigos de estado)
  • Consultas complejas que combinan múltiples condiciones con AND/OR
  • Operaciones COUNT
Index GenderIdx On Gender [ Type = bitmap ];
Index StatusIdx On Status [ Type = bitmap ];
Index StateIdx On State [ Type = bitmap ];

Requisitos y limitaciones:

  • La clase debe usar IDs enteros positivos asignados por el sistema (comportamiento por defecto)
  • No es adecuado para datos de alta cardinalidad (crearía cadenas de bits enormes)
  • Las operaciones INSERT/UPDATE/DELETE son algo más lentas debido a la manipulación de bits
  • No se puede usar en propiedades que excedan ~3.6M valores distintos

Una consulta como SELECT COUNT(*) FROM Patients WHERE Gender = 'F' AND State = 'CA' realiza un AND a nivel de bits rápido sobre dos índices bitmap.

Índices Bitslice

Los índices bitslice descomponen valores numéricos en su representación binaria, almacenando cada posición de bit por separado. Aceleran:

  • Agregación SUM
  • COUNT con condiciones sobre el valor indexado
  • Cálculos AVG (derivados de SUM/COUNT)
Index AmountBSIdx On Amount [ Type = bitslice ];

Una consulta como SELECT SUM(Amount) FROM Orders WHERE Status = 'Shipped' puede combinar un índice bitmap en Status con un índice bitslice en Amount para una ejecución extremadamente rápida.

Matriz de Decisión para Selección de Índice

Característica de los DatosTipo de Índice Recomendado
Alta cardinalidad (nombres, fechas, IDs)Estándar
Baja cardinalidad (género, estado, tipo)Bitmap
Agregación numérica (montos, cantidades)Bitslice
Consultas frecuentes de igualdad + rangoEstándar
Filtrado complejo AND/ORBitmap (en múltiples propiedades)
Datos de referencia raramente actualizadosBitmap (ideal)
Datos actualizados frecuentementeEstándar (menor sobrecarga de escritura)

Construcción y Reconstrucción de Índices

// Rebuild all indexes for a class
Do ##class(Sample.Person).%BuildIndices()

// Rebuild specific index
Do ##class(Sample.Person).%BuildIndices($ListBuild("NameIdx"))

// Purge and rebuild
Do ##class(Sample.Person).%PurgeIndices()
Do ##class(Sample.Person).%BuildIndices()

Referencias de Documentación

6. Usa métodos de índice único (unique index, %ExistsId)

Puntos Clave

  • Un índice único garantiza que dos objetos no pueden tener el mismo valor para la propiedad indexada
  • Los índices únicos generan automáticamente métodos `Exists` para verificar la existencia
  • %ExistsId(id) verifica si existe un objeto con un ID dado sin abrirlo
  • Los índices únicos se proyectan como restricciones SQL UNIQUE
  • El tipo de índice IdKey hace que las propiedades indexadas sean el ID del objeto

Notas Detalladas

Descripción general

Los índices únicos garantizan la integridad de los datos al prevenir valores duplicados. También proporcionan métodos eficientes para verificar la existencia. Comprender los índices únicos es importante tanto para el modelado de datos como para la optimización del rendimiento.

Definición de un Índice Único

Class Sample.Employee Extends %Persistent
{
    Property EmployeeID As %String [ Required ];
    Property SSN As %String [ Required ];
    Property Email As %String;

    /// Ensure no duplicate EmployeeIDs
    Index EmployeeIDIdx On EmployeeID [ Unique ];

    /// Ensure no duplicate SSNs
    Index SSNIdx On SSN [ Unique ];

    /// Compound unique index
    Index NameDOBIdx On (LastName, FirstName, DOB) [ Unique ];
}

Si intenta guardar un objeto con un valor duplicado en una propiedad con índice único, %Save() devuelve un error.

Métodos de Existencia Generados

Para cada índice único, el compilador de clases genera un método Exists:

// Check if an employee with EmployeeID "E12345" exists
If ##class(Sample.Employee).EmployeeIDIdxExists("E12345", .id) {
    Write "Employee exists with ID: ", id, !
}

El método Exists:

  • Toma el/los valor(es) de la propiedad indexada como entrada
  • Devuelve un booleano (1 = existe, 0 = no existe)
  • Opcionalmente devuelve el ID del objeto mediante un parámetro de salida

%ExistsId() para Verificaciones Basadas en ID

El método de clase %ExistsId() verifica si existe un objeto con un ID específico sin abrirlo:

If ##class(Sample.Employee).%ExistsId(42) {
    Write "Object 42 exists", !
} Else {
    Write "Object 42 does not exist", !
}

Esto es mucho más eficiente que abrir el objeto para verificar su existencia, ya que solo verifica el nodo del global sin cargar datos en memoria.

Índices IdKey

Un índice IdKey hace que las propiedades indexadas sean el identificador real del objeto en lugar del entero asignado por el sistema:

Class Sample.Country Extends %Persistent
{
    Property Code As %String(MAXLEN = 3);
    Property Name As %String(MAXLEN = 100);

    Index CodeIdx On Code [ IdKey ];
}

Con un índice IdKey:

  • Code se convierte en el ID del objeto: Set country = ##class(Sample.Country).%OpenId("USA")
  • No hay ID entero asignado por el sistema
  • Los índices bitmap no pueden usarse (los IDs no son enteros positivos)
  • La propiedad IdKey no puede modificarse después de guardar

Índice Único vs Clave Primaria en SQL

Un índice único se proyecta como una restricción SQL UNIQUE. Un índice IdKey se proyecta como PRIMARY KEY. Una clase puede tener múltiples índices únicos pero solo un IdKey.

Referencias de Documentación

7. Recuerda cómo las claves foráneas aplican integridad referencial (ForeignKey, OnDelete, NoAction, Cascade)

Puntos Clave

  • ForeignKey define una relación entre dos clases persistentes, garantizando que las referencias sean válidas
  • OnDelete controla el comportamiento cuando el objeto referenciado se elimina: NoAction, Cascade, SetNull
  • OnUpdate controla el comportamiento cuando la clave referenciada cambia (raramente usado ya que los IDs son inmutables)
  • Las claves foráneas se validan durante %Save() y se aplican en operaciones SQL
  • Las claves foráneas referencian un índice único o IdKey en la clase destino

Notas Detalladas

Descripción general

Las claves foráneas aplican integridad referencial al asegurar que las relaciones entre objetos permanezcan válidas. Cuando una clase declara una clave foránea, garantiza que el objeto referenciado existe en la clase destino antes de que el objeto que referencia pueda guardarse.

Definición de Claves Foráneas

Class Sample.Order Extends %Persistent
{
    Property OrderDate As %Date;
    Property CustomerID As %Integer;

    /// Foreign key referencing the Customer class
    ForeignKey CustomerFK(CustomerID)
        References Sample.Customer(CustomerIDIdx);
}

Class Sample.Customer Extends %Persistent
{
    Property CustomerID As %Integer;
    Property Name As %String;

    Index CustomerIDIdx On CustomerID [ Unique ];
}

La declaración de clave foránea especifica: 1. Un nombre para la restricción (CustomerFK) 2. La(s) propiedad(es) local(es) (CustomerID) 3. La clase referenciada y el índice único (Sample.Customer(CustomerIDIdx))

Acciones OnDelete

La palabra clave OnDelete controla qué sucede cuando un objeto referenciado (padre) se elimina:

ForeignKey CustomerFK(CustomerID)
    References Sample.Customer(CustomerIDIdx)
    [ OnDelete = cascade ];
Valor de OnDeleteComportamiento
noaction (por defecto)La eliminación falla si existen objetos que referencian
cascadeLos objetos que referencian se eliminan automáticamente
setnullLa propiedad de clave foránea en los objetos que referencian se establece en NULL
setdefaultLa propiedad de clave foránea se establece en su valor por defecto

Ejemplo de NoAction

// With NoAction (default), this fails if orders reference the customer
Set sc = ##class(Sample.Customer).%DeleteId(1)
// Returns error: "Foreign key constraint violation"

Ejemplo de Cascade

// With Cascade, deleting a customer also deletes all their orders
ForeignKey CustomerFK(CustomerID)
    References Sample.Customer(CustomerIDIdx)
    [ OnDelete = cascade ];

// Deleting customer 1 also deletes all orders where CustomerID = 1
Set sc = ##class(Sample.Customer).%DeleteId(1)

Validación en INSERT

Las claves foráneas se verifican en INSERT/SAVE. Intentar guardar un pedido con un CustomerID que no existe en la tabla Customer devuelve un error:

Set order = ##class(Sample.Order).%New()
Set order.CustomerID = 9999  // No such customer
Set sc = order.%Save()
// Error: foreign key constraint violation

Claves Foráneas Compuestas

Las claves foráneas pueden referenciar índices únicos compuestos (multi-propiedad):

ForeignKey LocationFK(Country, City)
    References Sample.Location(CountryCityIdx);

Consideraciones Importantes

  • Las claves foráneas agregan sobrecarga a las operaciones INSERT, UPDATE y DELETE
  • Se aplican tanto a nivel de objeto (%Save) como a nivel SQL
  • El índice referenciado debe ser un índice único o IdKey
  • Las claves foráneas se proyectan como restricciones SQL FOREIGN KEY
  • Úselas con criterio: mejoran la integridad de los datos pero impactan el rendimiento de escritura

Referencias de Documentación

Resumen de Preparación para el Examen

Conceptos Críticos a Dominar:

  1. MAXLEN por defecto para %String es 50; siempre establecerlo explícitamente para propiedades del mundo real
  2. TRUNCATE = 1 corta datos silenciosamente; TRUNCATE = 0 (por defecto) devuelve un error
  3. Tipos de stream: GlobalCharacter/GlobalBinary (base de datos), FileCharacter/FileBinary (sistema de archivos)
  4. E/S de streams: Write(), Read(), Rewind(), AtEnd, Size
  5. Calculated vs SqlComputed: Calculated no tiene almacenamiento; SqlComputed puede almacenarse e indexarse
  6. SqlComputeOnChange determina cuándo ocurre el recálculo
  7. La palabra clave Required causa que %Save() falle si la propiedad está vacía
  8. Los valores de InitialExpression entre llaves se evalúan en tiempo de ejecución
  9. Índices estándar para alta cardinalidad, bitmap para baja cardinalidad, bitslice para agregaciones
  10. Los índices bitmap requieren IDs enteros positivos
  11. Los índices únicos generan métodos Exists; %ExistsId() verifica por ID
  12. OnDelete de clave foránea: noaction (por defecto bloquea eliminación), cascade (elimina hijos), setnull

Escenarios Comunes en el Examen:

  • Elegir entre ajustar MAXLEN y una propiedad de tipo stream para datos grandes
  • Identificar si una propiedad computada será visible en SQL (necesita SqlComputed + SqlComputeCode)
  • Seleccionar el tipo de índice correcto dada una descripción de la distribución de datos
  • Predecir qué sucede cuando se elimina un objeto padre con restricciones de clave foránea
  • Determinar si %Save() tendrá éxito dadas las propiedades Required y los parámetros de validación

Recomendaciones de Práctica:

  • Crear propiedades con varias configuraciones de MAXLEN y probar condiciones límite
  • Construir una clase con propiedades de tipo stream y practicar operaciones Write/Read/Rewind
  • Crear propiedades Calculated y SqlComputed y consultarlas vía SQL para ver la diferencia
  • Configurar índices bitmap y estándar, luego comparar planes de consulta
  • Definir claves foráneas con diferentes acciones OnDelete y probar el comportamiento de eliminación

Report an Issue