9  Dimensiones Lentamente Cambiantes (SCD)

El capítulo anterior asumió, implícitamente, que las dimensiones son estáticas: un estudiante siempre pertenece a la misma carrera y un producto siempre a la misma categoría. En la práctica no es así: un cliente cambia de ciudad, un producto cambia de categoría y un empleado cambia de puesto.

Las Slowly Changing Dimensions (SCD) definen cómo tratar esos cambios de atributos descriptivos sin romper la relación entre un hecho y el contexto que tenía cuando ocurrió. La clave natural identifica a la entidad en el sistema fuente; la clave sustituta identifica una fila o versión concreta dentro del data warehouse.

Entidad, versiones y hechos históricos
Sistema fuentecliente_id = C-42Clave natural estable
Dimensióncliente_key = 101, 208...Una clave sustituta por versión
Tabla de hechoscliente_key = 101Conserva el contexto vigente

La clave sustituta desacopla el historial del identificador utilizado por el sistema fuente.

9.1 Tipos de SCD

9.1.1 Tipo 1: sobrescribir

SCD Tipo 1 actualiza la fila existente. El valor nuevo sustituye al anterior y todas las consultas, incluso las que analizan hechos antiguos, ven la descripción corregida o más reciente.

SCD Tipo 1: el nuevo valor reemplaza al anterior
AntesC-42 | Monterrey
DespuésC-42 | GuadalajaraMonterrey deja de estar disponible
Consultas históricasTambién muestran Guadalajara

Ventajas

  • Es el patrón más sencillo de implementar, consultar y mantener.
  • No incrementa el número de filas de la dimensión.
  • Corrige errores de captura para todo el historial de manera inmediata.
  • No requiere columnas de vigencia ni lógica temporal en los joins.

Desventajas

  • Destruye el valor anterior y no permite reconstruir cómo se veía la entidad en una fecha pasada.
  • Puede reinterpretar hechos históricos con atributos que todavía no existían cuando ocurrieron.
  • No es adecuado para auditoría, trazabilidad ni análisis de evolución.

Conviene usarlo cuando el cambio es una corrección ortográfica, una normalización o un atributo cuyo historial carece de valor analítico. No conviene usarlo para cambios de puesto, categoría, segmento o ubicación cuando esos cambios alteran la interpretación del hecho.

merge into dim_cliente as destino
using stg_cliente as fuente
  on destino.cliente_id = fuente.cliente_id
when matched then update set
  nombre = fuente.nombre,
  ciudad = fuente.ciudad
when not matched then insert (cliente_id, nombre, ciudad)
values (fuente.cliente_id, fuente.nombre, fuente.ciudad);

9.1.2 Tipo 2: crear una versión

SCD Tipo 2 cierra la versión vigente e inserta una fila nueva. La clave natural se mantiene, pero cada versión recibe una clave sustituta distinta y un intervalo de vigencia.

SCD Tipo 2: cada cambio crea una versión temporal
1 ene 2024
cliente_key: 101
cliente_id: C-42
ciudad: Monterrey
hasta: 2024-06-30
es_actual: false
1 jul 2024
Cambio detectado
Se cierra key 101
Se crea key 208
Versión vigente
cliente_key: 208
cliente_id: C-42
ciudad: Guadalajara
hasta: 9999-12-31
es_actual: true
Venta de mayo → key 101 → Monterrey  |  Venta de agosto → key 208 → Guadalajara

Ventajas

  • Conserva el historial completo de los atributos seleccionados.
  • Mantiene el contexto correcto de cada hecho sin depender de joins por rango de fecha, siempre que el proceso de carga asigne la clave sustituta vigente.
  • Permite auditoría, reconstrucción histórica y análisis de transiciones.
  • Hace explícita la vigencia mediante valido_desde, valido_hasta y es_actual.

Desventajas

  • Aumenta el volumen de la dimensión: una entidad puede producir muchas filas.
  • La carga es más compleja porque debe detectar cambios, cerrar la versión anterior e insertar la nueva de forma consistente.
  • Requiere reglas claras para cambios tardíos, correcciones retroactivas y límites de vigencia.
  • Puede generar versiones duplicadas, solapamientos o más de una fila vigente si el proceso no es idempotente o no se ejecuta de forma atómica.

Conviene usarlo cuando es necesario responder preguntas como “¿qué categoría tenía el producto cuando se realizó la venta?” o “¿en qué ciudad estaba registrado el cliente cuando ocurrió la transacción?”.

update dim_cliente as d
set valido_hasta = current_date - interval '1 day',
    es_actual = false
from stg_cliente as s
where d.cliente_id = s.cliente_id
  and d.es_actual
  and (d.nombre is distinct from s.nombre
       or d.ciudad is distinct from s.ciudad);

insert into dim_cliente
  (cliente_id, nombre, ciudad, valido_desde, valido_hasta, es_actual)
select s.cliente_id,
       s.nombre,
       s.ciudad,
       current_date,
       date '9999-12-31',
       true
from stg_cliente as s
left join dim_cliente as d
  on d.cliente_id = s.cliente_id
 and d.es_actual
where d.cliente_id is null
   or d.nombre is distinct from s.nombre
   or d.ciudad is distinct from s.ciudad;

En producción, el cierre y la inserción deberían ejecutarse dentro de una misma transacción cuando el motor lo permita. La clave sustituta puede generarse mediante una columna identity, una secuencia, un hash estable de la versión o la estrategia definida por la plataforma.

9.1.3 Tipo 3: guardar el valor anterior

SCD Tipo 3 conserva el valor vigente y un valor previo en la misma fila. No crea una historia ilimitada: al llegar otro cambio, el valor anterior también se sobrescribe.

SCD Tipo 3: una fila conserva el estado actual y el previo
Antes del cambioactual = Monterreyanterior = NULL
Después del cambioactual = Guadalajaraanterior = MonterreyUn tercer valor desplazaría a Monterrey

Ventajas

  • Permite comparar fácilmente el estado actual con el inmediatamente anterior.
  • Mantiene una sola fila por entidad y evita el crecimiento de SCD Tipo 2.
  • Las consultas son sencillas porque no necesitan intervalos de vigencia ni múltiples versiones.

Desventajas

  • Sólo conserva una cantidad fija y limitada de cambios.
  • Requiere agregar columnas específicas para cada atributo histórico.
  • Pierde utilidad cuando varios atributos cambian de forma independiente.
  • No permite reconstruir el estado de la dimensión para una fecha arbitraria.

Conviene usarlo cuando el negocio sólo compara el valor actual contra el anterior, por ejemplo, segmento actual frente a segmento previo. No sustituye a SCD Tipo 2 cuando existe un requisito de auditoría completa.

update dim_cliente as d
set ciudad_anterior = d.ciudad_actual,
    ciudad_actual = s.ciudad,
    fecha_cambio = current_date
from stg_cliente as s
where d.cliente_id = s.cliente_id
  and d.ciudad_actual is distinct from s.ciudad;

9.2 Implementación con dbt

dbt materializa el modelo dimensional desde tablas de staging. Un modelo incremental puede cubrir SCD Tipo 1; para SCD Tipo 2, un snapshot registra automáticamente las versiones detectadas.

Flujo de SCD Tipo 2 con dbt
Stagingstg_clientesDatos normalizados
Snapshotunique_keycheck_colsDetecta y versiona cambios
Dimensión finalcliente_keyvalido_desde / hastaInterfaz para hechos y consultas

Las pruebas se aplican después de materializar la dimensión y verifican tanto las claves como la integridad temporal.

-- snapshots/clientes.sql
{% snapshot clientes_snapshot %}
{{
  config(
    target_schema='snapshots',
    unique_key='cliente_id',
    strategy='check',
    check_cols=['nombre', 'ciudad']
  )
}}

select cliente_id, nombre, ciudad
from {{ ref('stg_clientes') }}
{% endsnapshot %}

El snapshot agrega dbt_valid_from, dbt_valid_to y dbt_scd_id. La dimensión final usa esas columnas para exponer la vigencia y la clave sustituta.

-- models/marts/dim_cliente.sql
select
  dbt_scd_id as cliente_key,
  cliente_id,
  nombre,
  ciudad,
  dbt_valid_from as valido_desde,
  coalesce(dbt_valid_to, timestamp '9999-12-31') as valido_hasta,
  dbt_valid_to is null as es_actual
from {{ ref('clientes_snapshot') }}

9.2.1 Ventajas de usar snapshots de dbt

  • Centralizan la detección y el versionado de cambios.
  • Reducen la cantidad de SQL procedural escrito manualmente.
  • Generan metadatos de vigencia de forma consistente.
  • Se integran con dependencias, documentación y pruebas del proyecto dbt.

9.2.2 Desventajas y precauciones

  • El comportamiento depende de elegir correctamente unique_key, strategy y check_cols.
  • Incluir columnas volátiles, como una marca de tiempo de carga, puede crear versiones innecesarias en cada ejecución.
  • Un snapshot conserva lo que observa durante sus ejecuciones; no puede reconstruir cambios que ocurrieron y desaparecieron entre dos corridas.
  • Los cambios retroactivos y las eliminaciones requieren una política explícita.

9.3 Pruebas de integridad

Las pruebas declarativas verifican que la clave sustituta sea única y no nula, y que la clave natural esté presente.

models:
  - name: dim_cliente
    columns:
      - name: cliente_key
        tests: [unique, not_null]
      - name: cliente_id
        tests: [not_null]

Esas pruebas no garantizan por sí solas que exista una sola versión vigente. Esa regla puede verificarse con una prueba singular adicional:

-- tests/una_version_vigente_por_cliente.sql
select cliente_id
from {{ ref('dim_cliente') }}
where es_actual
group by cliente_id
having count(*) > 1

También conviene comprobar que los intervalos no se solapen y que valido_desde sea menor que valido_hasta.

9.4 Comparación y criterio de elección

Comparación de los tipos principales
Tipo Historial Filas por entidad Complejidad Uso principal
Tipo 1 Ninguno Una Baja Correcciones y atributos sin valor histórico
Tipo 2 Completo Una por versión Alta Auditoría y contexto histórico preciso
Tipo 3 Limitado Una Media Comparar el estado actual con el previo

La elección no tiene que hacerse para toda la dimensión. En una misma tabla es posible aplicar Tipo 1 a atributos correctivos, Tipo 2 a atributos cuyo historial importa y, de forma excepcional, Tipo 3 a comparaciones limitadas. Lo importante es definir la política por atributo, documentar por qué se conserva o descarta el historial y diseñar las pruebas que protejan esa decisión.