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.
cliente_id = C-42Clave natural establecliente_key = 101, 208...Una clave sustituta por versióncliente_key = 101Conserva el contexto vigenteLa 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.
C-42 | MonterreyC-42 | GuadalajaraMonterrey deja de estar disponibleVentajas
- 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.
cliente_id: C-42
ciudad: Monterrey
hasta: 2024-06-30
es_actual: false
Se cierra key 101
Se crea key 208
cliente_id: C-42
ciudad: Guadalajara
hasta: 9999-12-31
es_actual: true
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_hastayes_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.
actual = Monterreyanterior = NULLactual = Guadalajaraanterior = MonterreyUn tercer valor desplazaría a MonterreyVentajas
- 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.
stg_clientesDatos normalizadosunique_keycheck_colsDetecta y versiona cambioscliente_keyvalido_desde / hastaInterfaz para hechos y consultasLas 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,strategyycheck_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(*) > 1Tambié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
| 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.