Saltar al contenido

lección 5

Slowly Changing Dimensions: cuando el mundo cambia

Aprende a manejar cambios en las dimensiones: qué pasa cuando un cliente cambia de país o un producto cambia de categoría.

⏱ 30 min

Hasta ahora hemos tratado las dimensiones como si fueran estáticas: un cliente SIEMPRE vive en Madrid, un producto SIEMPRE pertenece a la categoría "Electrónica". Pero la realidad es más cruel. Los clientes se mudan. Los productos se recategorizan. Las tiendas cambian de gerente. Los empleados cambian de departamento. ¿Qué hacemos cuando el mundo cambia y nuestro warehouse tiene que reflejarlo?

Este problema se llama Slowly Changing Dimensions (SCD) — dimensiones que cambian lentamente. "Lentamente" porque no cambian cada segundo (eso sería un hecho), sino ocasionalmente: un cliente cambia de dirección una vez al año, un producto se recategoriza una vez cada pocos meses. Kimball definió 7 tipos de SCD, pero en la práctica usarás 3: tipo 1 (sobrescribir), tipo 2 (historificar) y tipo 3 (columnas anterior/actual).

## SCD Tipo 0: no hacer nada (atributos fijos)

Algunos atributos NUNCA deberían cambiar: la fecha de nacimiento de un cliente, el código EAN de un producto, la fecha de apertura de una tienda. Estos son atributos "fijos" — si alguien intenta cambiarlos, probablemente es un error de datos. Para estos, simplemente no permites actualizaciones. Es el caso más simple y suele aplicarse a identifiers naturales.

## SCD Tipo 1: sobrescribir el valor anterior

El tipo más simple: cuando el atributo cambia, sobrescribes el valor anterior con el nuevo. El histórico se PIERDE. Si un cliente se muda de Madrid a Barcelona, actualizas city="Barcelona" y ya no sabes que antes vivía en Madrid.

1-- SCD Tipo 1: Sobrescribir (se pierde el histórico)
2
3-- Antes del cambio:
4-- | customer_key | name | city | segment |
5-- | 42 | Ana López | Madrid | VIP |
6
7-- Ana se muda a Barcelona → UPDATE directo
8UPDATE dim_customer
9SET city = 'Barcelona'
10WHERE customer_key = 42;
11
12-- Después del cambio:
13-- | customer_key | name | city | segment |
14-- | 42 | Ana López | Barcelona | VIP |
15
16-- Problema: TODAS las ventas históricas de Ana ahora "pertenecen"
17-- a Barcelona, incluso las que hizo cuando vivía en Madrid.
18-- Si alguien consulta "ventas en Madrid 2023" ya no incluirá a Ana.

SCD Tipo 1 es simple pero destructivo: pierdes la historia. Úsalo solo cuando el histórico no importa.

¿Cuándo usar Tipo 1? Cuando el histórico NO importa para el análisis. Ejemplos: corrección de typos (customer_name tenía un error), actualización de email, campos que eran incorrectos desde el principio. También se usa para atributos donde el negocio siempre quiere ver el valor ACTUAL (segmento de cliente actualizado por un modelo de ML cada mes).

## SCD Tipo 2: preservar toda la historia

El tipo más poderoso y más usado en warehouses serios: cuando un atributo cambia, NO sobrescribes. En su lugar, "cierras" la fila actual (marcándola como no vigente) y creas una NUEVA fila con el valor actualizado. El cliente ahora tiene DOS filas en dim_customer: una para cuando vivía en Madrid y otra para Barcelona. Cada una con su propio surrogate key.

1-- SCD Tipo 2: Historificar (preserva TODA la historia)
2
3-- Antes: Ana vive en Madrid
4-- | customer_key | customer_id | name | city | is_current | valid_from | valid_to |
5-- | 42 | C-001 | Ana López | Madrid | TRUE | 2020-03-15 | 9999-12-31 |
6
7-- Ana se muda a Barcelona el 2024-06-01:
8-- Paso 1: "Cerrar" la fila actual
9UPDATE dim_customer
10SET is_current = FALSE,
11 valid_to = '2024-05-31'
12WHERE customer_key = 42;
13
14-- Paso 2: Insertar nueva fila con nuevo surrogate key
15INSERT INTO dim_customer (customer_id, name, city, is_current, valid_from, valid_to)
16VALUES ('C-001', 'Ana López', 'Barcelona', TRUE, '2024-06-01', '9999-12-31');
17-- Se genera customer_key = 1847 (nuevo surrogate key)
18
19-- Resultado:
20-- | customer_key | customer_id | name | city | is_current | valid_from | valid_to |
21-- | 42 | C-001 | Ana López | Madrid | FALSE | 2020-03-15 | 2024-05-31 |
22-- | 1847 | C-001 | Ana López | Barcelona | TRUE | 2024-06-01 | 9999-12-31 |
23
24-- Ahora las ventas de Ana en 2023 apuntan a customer_key=42 (Madrid)
25-- y las ventas de 2024 en adelante apuntan a customer_key=1847 (Barcelona)
26-- ¡La historia está preservada!

SCD Tipo 2 crea versiones. El surrogate key es la clave para que el histórico funcione.

La magia del Tipo 2 está en las surrogate keys. Las ventas antiguas de Ana apuntan a customer_key=42 (versión Madrid). Las ventas nuevas apuntarán a customer_key=1847 (versión Barcelona). El histórico se preserva AUTOMÁTICAMENTE sin tocar la fact table. Cuando alguien consulta "ventas en Madrid 2023", Ana aparece porque customer_key=42 tiene city=Madrid.

## SCD Tipo 3: columnas anterior/actual

Un compromiso intermedio: guardas el valor ANTERIOR en una columna extra. No necesitas filas nuevas, pero solo puedes ver UN cambio hacia atrás. Es útil cuando el negocio quiere comparar "antes vs ahora" pero no necesita todo el historial completo.

1-- SCD Tipo 3: Columnas anterior/actual
2
3-- | customer_key | name | current_city | previous_city | city_changed_on |
4-- | 42 | Ana López | Barcelona | Madrid | 2024-06-01 |
5
6-- Ventaja: simple, una sola fila por cliente
7-- Desventaja: solo 1 cambio en la historia. Si Ana se muda otra vez
8-- (Barcelona → Valencia), pierdes que estuvo en Madrid.

Tipo 3 es un compromiso: ves un antes/después, pero no el historial completo.

El error que más cuesta dinero: aplicar SCD Tipo 1 a atributos que SÍ importan históricamente. He visto una cadena de retail que sobrescribía la región de sus tiendas. Cuando reorganizaron las regiones, TODOS los históricos de ventas se reasignaron. Los reportes de performance regional se volvieron inútiles — "la región Norte creció 40%" era mentira, simplemente le habían asignado tiendas nuevas. La solución fue un SCD Tipo 2 y una migración dolorosa de 6 semanas.

## Cuándo usar cada tipo: guía práctica

  • Tipo 0: atributos que NUNCA cambian (fecha de nacimiento, código EAN)
  • Tipo 1: correcciones de errores, atributos donde solo importa el valor actual (email, teléfono)
  • Tipo 2: atributos críticos para el análisis histórico (segmento, región, categoría, precio)
  • Tipo 3: atributos donde necesitas comparar antes/ahora pero no todo el historial (plan de suscripción)
  • Combinación: es COMÚN usar tipos diferentes para distintos atributos de la MISMA dimensión

Mi regla personal: si el CFO puede preguntarte "¿cómo eran las ventas por X cuando Y tenía el valor anterior?", necesitas Tipo 2 para ese atributo. Si la pregunta es "¿cuál es el valor actual de X?", Tipo 1 es suficiente. En la duda, Tipo 2 — guardar historia es barato, reconstruirla después es carísimo.

## Implementación de SCD Tipo 2 en SQL

1-- Patrón completo de SCD Tipo 2: cuatro sentencias, en este orden
2
3-- Paso 1: materializar la foto de la fuente y los cambios detectados.
4-- Tienen que ser tablas temporales, no CTEs: una CTE solo vive dentro de la
5-- sentencia donde se declara, y aquí las necesitan los tres pasos siguientes.
6CREATE TEMP TABLE source_data AS
7SELECT customer_id, name, city, segment
8FROM staging_customers; -- datos frescos del sistema fuente
9
10CREATE TEMP TABLE changes AS
11SELECT s.*
12FROM source_data s
13JOIN dim_customer d ON s.customer_id = d.customer_id AND d.is_current = TRUE
14WHERE s.city != d.city OR s.segment != d.segment; -- qué atributos disparan versión
15
16-- Paso 2: cerrar la versión vigente de los que cambiaron
17UPDATE dim_customer
18SET is_current = FALSE,
19 valid_to = CURRENT_DATE - INTERVAL '1 day'
20WHERE is_current = TRUE
21 AND customer_id IN (SELECT customer_id FROM changes);
22
23-- Paso 3: insertar la nueva versión de esos mismos clientes
24INSERT INTO dim_customer (customer_id, name, city, segment, is_current, valid_from, valid_to)
25SELECT customer_id, name, city, segment, TRUE, CURRENT_DATE, '9999-12-31'::DATE
26FROM changes;
27
28-- Paso 4: insertar los clientes que no existían todavía en la dimensión
29INSERT INTO dim_customer (customer_id, name, city, segment, is_current, valid_from, valid_to)
30SELECT s.customer_id, s.name, s.city, s.segment, TRUE, CURRENT_DATE, '9999-12-31'::DATE
31FROM source_data s
32WHERE NOT EXISTS (
33 SELECT 1 FROM dim_customer d WHERE d.customer_id = s.customer_id
34);
35
36DROP TABLE changes;
37DROP TABLE source_data;

Patrón ETL para SCD Tipo 2: detectar cambios → cerrar vigentes → insertar nuevas versiones.

Regístrate para guardar tu progreso.