T1.2: Designs Advanced Schemas

Knowledge Review - InterSystems IRIS SQL Specialist

1. Diferencia tipos de datos de fecha y hora (%Date, %Time, %TimeStamp, %PosixTime)

Puntos Clave

  • %Date: Almacena solo fecha, valor lógico es días enteros desde el 31 de diciembre de 1840
  • %Time: Almacena solo hora, valor lógico es segundos pasados desde medianoche (0-86399)
  • %TimeStamp: Fecha y hora combinadas en formato YYYY-MM-DD HH:MM:SS.nnnnnnnnn
  • %PosixTime: Marca de tiempo codificada de 64 bits, segundos transcurridos desde 1970-01-01 00:00:00
  • Cada tipo tiene valores SqlCategory distintos: DATE, TIME, TIMESTAMP y POSIXTS

Notas Detalladas

Tipo de Datos %Date

El tipo de datos %Date almacena valores solo de fecha con una representación lógica como un entero que representa el número de días desde el 31 de diciembre de 1840 (día 0).

  • Formato de almacenamiento: Representación entera compacta
  • Rango de fechas: Soporta fechas desde 0001-01-01 hasta 9999-12-31
  • Fechas anteriores a 1840: La época (día 0) es 1840-12-31, pero el entero tiene signo — las fechas anteriores a 1840-12-31 se almacenan como número de días negativo. Por ejemplo, 0001-01-01 corresponde aproximadamente al día -671.861. Por eso el rango soportado se extiende mucho antes de 1840.
  • Formato de visualización: Formatos específicos de configuración regional o formato ODBC (YYYY-MM-DD)
  • SqlCategory: DATE

Tipo de Datos %Time

El tipo de datos %Time representa valores de hora del día, almacenando el valor lógico como el número de segundos pasados desde medianoche.

  • Rango de valores: 0 a 86399 segundos
  • Formato de visualización: HH:MM:SS
  • SqlCategory: TIME
  • Caso de uso: Aritmética y comparaciones de tiempo eficientes

Tipo de Datos %TimeStamp

El tipo de datos %TimeStamp combina componentes de fecha y hora en un solo valor.

  • Formato: YYYY-MM-DD HH:MM:SS.nnnnnnnnn
  • Precisión: Hasta nueve dígitos de segundos fraccionarios
  • Conversión: Valor lógico obtenido del formato $HOROLOG usando $ZDATETIME(h,3)
  • SqlCategory: TIMESTAMP
  • Caso de uso: Registrar momentos exactos en el tiempo (formato de marca de tiempo ODBC estándar)

Tipo de Datos %PosixTime

El tipo de datos %PosixTime ofrece una representación alternativa de marca de tiempo usando codificación de época Unix.

  • Almacenamiento: Entero con signo codificado de 64 bits que representa segundos transcurridos con precisión de microsegundos
  • Época: 1970-01-01 00:00:00 (época Unix, NO la época 1840 de %Date). La forma codificada en la época es 1152921504606846976; la codificación empaqueta el signo, los segundos y los microsegundos en 64 bits.
  • Rango de fechas: 0001-01-01 a 9999-12-31 (el mismo rango que otros tipos temporales, logrado mediante el rango del entero de 64 bits con signo)
  • Precisión: Exactamente 6 dígitos de segundos fraccionarios (microsegundos)
  • SqlCategory: POSIXTS
  • Nota: Las fechas anteriores a 1970 se codifican con valores con signo negativo. %PosixTime y %Date usan épocas diferentes, por lo que no se debe convertir entre ellas restando valores; usar TO_POSIXTIME() / TO_DATE() para conversiones correctas.

SqlCategory y Conversión de Tipos

Cada tipo de datos tiene una clasificación SqlCategory distinta que controla operaciones de comparación.

  • Funciones de conversión: Usar DATE(), TO_TIMESTAMP(), o TO_POSIXTIME() al mezclar tipos temporales en comparaciones o índices
  • Impacto en diseño de esquema: La elección del tipo de datos temporal afecta la eficiencia de almacenamiento, rendimiento de consultas e interoperabilidad con sistemas externos
  • Recomendaciones:
  • %Date y %Time: Almacenamiento más compacto para valores solo de fecha u hora
  • %TimeStamp: Marcas de tiempo de precisión legibles por humanos
  • %PosixTime: Compatibilidad con marcas de tiempo Unix y cálculos temporales de alta precisión

2. Propiedades de Colección (list Of, array Of)

Puntos Clave

  • Dos formas integradas de propiedades de colección: `Property X As list Of %Type` y `Property X As array Of %Type`.
  • `list Of`: se almacena en línea en la fila del padre como una única columna codificada con `$LISTBUILD`. No se crea tabla hija.
  • `array Of`: se almacena como tabla hija separada con una clave foránea hacia el padre — cada elemento del array es una fila.
  • Buscar dentro de un `list Of` en SQL: usar el predicado `FOR SOME %ELEMENT(list) (%Value = 'X')`. No usar `LIKE '%X%'` contra el blob $LISTBUILD — buscaría bytes crudos y daría resultados erróneos.
  • Indexar dentro de colecciones: usar un índice de colección (`Index i On Allergies(ELEMENTS)` para list, o bitmap sobre la tabla hija para array).

Notas Detalladas

list Of %String — almacenamiento en línea

Class App.Patient Extends %Persistent {
  Property Name As %String;
  Property Allergies As list Of %String;
}

Vista SQL de la tabla:

IDNameAllergies
1Smith$LB("Penicilina","Maní")

La columna Allergies contiene el blob codificado con $LISTBUILD directamente; no hay tabla hija. Para buscar:

SELECT ID, Name FROM App.Patient
 WHERE FOR SOME %ELEMENT(Allergies) (%Value = 'Penicilina')

array Of %String — almacenamiento en tabla hija

Class App.Patient Extends %Persistent {
  Property Name As %String;
  Property Phones As array Of %String;
}

IRIS crea una tabla hija App.Patient_Phones con columnas (Patient, element_key, Phones). Cada teléfono es una fila. JOIN/WHERE estándar funcionan sobre la tabla hija.

Comparación

Aspectolist Ofarray Of
Almacenamiento SQLcolumna en línea, codificada $LISTBUILDtabla hija separada
Tiene claveposicional (ordinal)con clave (arbitraria por elemento)
Uso típicomulti-valor pequeño y fijo (alergias, etiquetas)atributos con clave (teléfonos por tipo)
BúsquedaFOR SOME %ELEMENTJOIN sobre tabla hija
Indexadoíndice de elemento de colección sobre el padreíndice sobre la tabla hija

Referencias de Documentación

3. Objetos Seriales (%SerialObject)

Puntos Clave

  • Una clase `%SerialObject` define una estructura reutilizable (p. ej. `Address`) que se embebe en la fila padre en lugar de almacenarse como objeto persistente separado.
  • Las propiedades de un %SerialObject se serializan con `$LISTBUILD` y se almacenan como una única columna en el padre.
  • SQL proyecta la estructura como columnas aplanadas con guiones bajos: `HomeAddress_Street`, `HomeAddress_City`, etc.
  • Sin tabla separada; sin clave foránea. No se puede `SELECT * FROM App.Address` — un %SerialObject no tiene extent propio.
  • Usar cuando la estructura embebida no tiene identidad propia y siempre pertenece a su padre (direcciones, dinero-con-moneda, teléfono-con-extensión).

Notas Detalladas

Class App.Address Extends %SerialObject {
  Property Street As %String;
  Property City As %String;
}

Class App.Person Extends %Persistent {
  Property Name As %String;
  Property HomeAddress As App.Address;
}

Proyección SQL de App.Person:

IDNameHomeAddress_StreetHomeAddress_City
1SmithCalle Mayor 42Madrid

Para consultar un campo interno: SELECT Name, HomeAddress_City FROM App.Person. Las columnas aplanadas con guión bajo son direccionables desde SQL; la clase App.Address en sí no es una tabla.

Referencias de Documentación

4. Propiedades de Stream (%Stream.GlobalCharacter, %Stream.GlobalBinary)

Puntos Clave

  • Las propiedades de stream (`%Stream.GlobalCharacter`, `%Stream.GlobalBinary`) están diseñadas para valores mayores que el límite de 3,6 MB de cadena — documentos, imágenes, blobs serializados.
  • Se almacenan separadamente de los datos principales de la fila; la fila contiene solo un localizador.
  • Limitaciones en SQL: los streams no pueden usarse directamente en `WHERE`, `ORDER BY`, `GROUP BY`, ni en la mayoría de expresiones. NO son indexables en su totalidad.
  • Soportado en SQL: `SELECT` (recupera el stream), `INSERT`/`UPDATE` con un valor de stream, funciones de longitud/posición donde aplican.
  • Para buscar DENTRO de un stream, usar `%CONTAINS` con un índice iFind/texto configurado (`Index ci On (Content) As %iFind.Index.Basic`), no `LIKE`.

Notas Detalladas

Class App.Document Extends %Persistent {
  Property Title As %String;
  Property Content As %Stream.GlobalCharacter;
  Index ci On (Content) As %iFind.Index.Basic;
}

Operaciones típicas:

-- OK: recuperar stream (el cliente ensambla el stream desde el cursor)
SELECT Title, Content FROM App.Document WHERE ID = 1

-- NO soportado: WHERE sobre columna stream directamente
SELECT * FROM App.Document WHERE Content LIKE '%confidencial%'   -- error o resultados incorrectos

-- Forma correcta: %CONTAINS con índice iFind
SELECT ID, Title FROM App.Document WHERE %ID %FIND search_index(ci, 'confidencial')

Referencias de Documentación

5. Propiedades Computadas y Calculadas

Puntos Clave

  • `Calculated`: el valor se computa cada vez que se lee; no hay almacenamiento. Requiere un accesor `Get()` (o `SqlComputeCode` del que IRIS genera uno automáticamente).
  • `SqlComputed`: IRIS genera el valor almacenado a partir de `SqlComputeCode`. Combinado con `SqlComputeOnChange` (listando las propiedades dependientes), el valor se recalcula automáticamente cuando cambian esas propiedades — y se persiste en la columna, de modo que las consultas SQL son rápidas.
  • `Transient`: solo en memoria; no se almacena, no se proyecta a SQL.
  • Regla práctica: usar `Calculated` para derivaciones siempre-frescas que NO necesitan consultarse; usar `SqlComputed` + `SqlComputeOnChange` cuando el valor derivado deba ser indexable / filtrable con WHERE en tablas grandes.
  • Importante: una propiedad `Calculated` NO participa en índices SQL (no hay nada que indexar). Si `WHERE Age = 30` debe usar un índice en una tabla de 10M filas, usar `SqlComputed`, no `Calculated`.

Notas Detalladas

// FullName — siempre fresco, recomputado en lectura, no indexable
Property FullName As %String [ Calculated,
  SqlComputeCode = {Set {*} = {FirstName}_" "_{LastName}},
  SqlComputed ];

// Age — almacenado, auto-actualizado cuando cambia DateOfBirth, indexable
Property Age As %Integer [
  SqlComputed,
  SqlComputeCode = {Set {*} = $SYSTEM.SQL.Functions.DATEDIFF("yy",{DateOfBirth},$HOROLOG)},
  SqlComputeOnChange = DateOfBirth ];
Index AgeIdx On Age;

Para una tabla de 10M filas consultada frecuentemente con WHERE Age = 30, la segunda forma es correcta — el valor computado se materializa en la fila, y un índice sobre Age hace que la búsqueda sea O(log n) en lugar de full table scan.

Referencias de Documentación

6. Almacenamiento Columnar y Cargas OLTP vs Analíticas

Puntos Clave

  • El almacenamiento por defecto en IRIS es orientado a filas — ideal para OLTP (inserts rápidos, lookups por clave primaria, recuperación de filas completas).
  • Para cargas analíticas (agregaciones sobre millones de filas en pocas columnas), el almacenamiento columnar es mucho más eficiente: compresión, escaneos vectorizados y layout de memoria amigable para CPU.
  • Habilitar almacenamiento columnar por clase (`Storage Columnar`) o por columna (`[ Columnar ]` sobre una propiedad) según versión/necesidad.
  • Cargas mixtas** (inserts OLTP rápidos + informes analíticos periódicos) suelen beneficiarse de **almacenamiento en capas: row-store en el camino caliente, columnar en columnas específicas para agregación.
  • El almacenamiento columnar paga dividendos en consultas como `SELECT SUM(Amount) FROM Sales WHERE RegionId = ...` sobre una tabla grande. NO ayuda mucho en `SELECT * FROM Sales WHERE ID = 1`.

Notas Detalladas

El almacenamiento columnar se configura directamente en DDL — no hace falta una definición de clase.

-- Mixto: tabla row-storage con columnas en columnar para las que dirigen la agregación
CREATE TABLE App.Sale (
  OrderId  INTEGER PRIMARY KEY,
  RegionId INTEGER,
  Amount   NUMERIC(18,2) WITH STORAGETYPE = COLUMNAR,
  Quantity INTEGER       WITH STORAGETYPE = COLUMNAR
);

-- Tabla totalmente columnar — cargas analíticas puras
CREATE TABLE App.SalesFact (
  Id       BIGINT PRIMARY KEY,
  RegionId INTEGER,
  Amount   NUMERIC(18,2),
  Quantity INTEGER
) WITH STORAGETYPE = COLUMNAR;

-- Añadir una nueva columna columnar a una tabla existente
ALTER TABLE App.Sale ADD COLUMN Discount NUMERIC(18,2) WITH STORAGETYPE = COLUMNAR;

El mismo efecto se puede expresar en una definición de clase ObjectScript (Property Amount As %Numeric(SCALE=2) [ Columnar ] para una columna, o el flag [ StorageStrategy = Columnar ] a nivel de clase para una tabla totalmente columnar) — ambos caminos producen el mismo layout físico.

Referencias de Documentación

7. Control de Concurrencia Optimista

Puntos Clave

  • Concurrencia optimista: los lectores no bloquean — en el update, se verifica que la fila no haya cambiado desde la lectura.
  • Añadir una columna de versión (`%Integer`/`%TimeStamp`) y declararla vía el parámetro de clase `VERSIONPROPERTY` (o la palabra clave de clase `VersionProperty`). IRIS la incrementa automáticamente en cada save exitoso.
  • La lógica de UPDATE usa entonces `WHERE ID = :id AND Version = :readVersion` — si cero filas se actualizan, otro usuario se adelantó, y la aplicación reintenta/mezcla/lanza error.
  • Alternativa: calcular un hash MD5/SHA de la fila y revalidar en el update. Más pesado; usar VersionProperty cuando se pueda.
  • El bloqueo pesimista (`SELECT ... FOR UPDATE`) es la estrategia opuesta — más simple para secciones críticas cortas pero bloquea a otros lectores.

Notas Detalladas

Class App.Order Extends %Persistent [ VersionProperty = Version ] {
  Property Quantity As %Integer;
  Property Version As %Integer [ InitialExpression = 0 ];
}

Patrón de aplicación:

-- Lectura
SELECT Quantity, Version INTO :qty, :ver FROM App.Order WHERE ID = :id

-- ... el usuario edita ...

-- Update protegido por la versión
UPDATE App.Order SET Quantity = :newQty WHERE ID = :id AND Version = :ver
IF %ROWCOUNT = 0 THEN
   -- lost-update: otra sesión modificó la fila
END

Referencias de Documentación

Resumen de Preparación para el Examen

Conceptos Críticos a Dominar:

  1. Formatos de Almacenamiento Lógico: Saber que %Date almacena días desde el 31 de dic de 1840, %Time almacena segundos desde medianoche, %TimeStamp usa formato ODBC, %PosixTime usa marca de tiempo Unix codificada
  2. Valores SqlCategory: Memorizar las cuatro categorías distintas (DATE, TIME, TIMESTAMP, POSIXTS) y su impacto en comparaciones
  3. Diferencias de Precisión: %TimeStamp soporta hasta 9 dígitos de segundos fraccionarios, %PosixTime codifica exactamente 6 dígitos, %Time no tiene segundos fraccionarios
  4. Funciones de Conversión: Comprender cuándo usar DATE(), TO_TIMESTAMP(), TO_POSIXTIME() para conversión de tipos
  5. Selección de Caso de Uso: Saber cuándo elegir cada tipo - %Date para solo fechas, %Time para solo horas, %TimeStamp para marcas de tiempo generales, %PosixTime para compatibilidad Unix

Escenarios Comunes del Examen:

  • Seleccionar el tipo de datos temporal apropiado para un requisito de aplicación dado
  • Convertir entre diferentes formatos temporales usando funciones SQL
  • Comprender las limitaciones de rango y precisión de cada tipo
  • Reconocer la naturaleza codificada de valores %PosixTime
  • Trabajar con variables especiales $HOROLOG y $ZTIMESTAMP en contexto SQL

Recomendaciones de Práctica Práctica:

  • Crear tablas con columnas de cada tipo temporal e insertar datos de muestra
  • Practicar conversiones entre tipos usando DATE(), TO_TIMESTAMP() y TO_POSIXTIME()
  • Experimentar con precisión de segundos fraccionarios en %TimeStamp y %PosixTime
  • Consultar datos temporales con diferentes modos de visualización (Logical, ODBC, Display)
  • Comparar rendimiento y características de almacenamiento de cada tipo
  • Trabajar con fechas antes de 1970 para comprender valores negativos de %PosixTime

Report an Issue