Cómo crear una Slowly Changing Fact Table Tipo 3 en Modelado de Datos (Kimball)
Este tutorial explica cómo crear una Slowly Changing Fact Table Tipo 3 en Modelado de Datos (Kimball) para preservar valores históricos limitados de atributos dentro de la propia fact table. El patrón es útil cuando necesita comparar un valor actual con un valor anterior puntual (por ejemplo, precio anterior) sin recurrir a una estructura SCD Tipo 2 completa que duplica filas. Aquí mostramos por qué, cuándo usarlo y un conjunto práctico de pasos y ejemplos SQL para implementar y validar la solución.
Requisitos previos
- Conocimientos básicos de Modelado Dimensional Kimball y SQL.
- Entorno con una base de datos relacional (por ejemplo SQL Server, PostgreSQL) para pruebas. Idealmente con datos de prueba de 10k–1M filas para validar rendimiento.
- Ejemplos de datos de transacciones y dimensiones (dim_customer, dim_product, dim_date) y una noción de la tasa de cambio (por ejemplo, 2–10% de los productos cambian de precio por mes).
- Herramientas ETL/ELT (por ejemplo, scripts SQL, Azure Data Factory, dbt) para automatizar actualizaciones.
Paso 1: Entender el patrón Slowly Changing Fact Table Tipo 3
Un SCD Tipo 3 en la fact table guarda un atributo actual y un atributo anterior (o N versiones limitadas) dentro de la propia fact. En lugar de crear filas históricas (Tipo 2) que aumentan el volumen de filas y complican agregaciones, el Tipo 3 mantiene columnas como price_current y price_previous. Esto es adecuado para análisis de variación puntual (por ejemplo, diferencia de precio entre hoy y la última modificación) y para informes que no requieren un historial completo con todas las versiones anteriores.
Paso 2: Elegir qué atributos versionar
Seleccione atributos que cambian ocasionalmente y en los que solo importe la última modificación anterior: precio, calificación de riesgo, estado de elegibilidad, promo_code activo. Ejemplos concretos: si el 95% de las transacciones necesitan solo comparar precio actual vs anterior, mantener price_current/price_previous reduce el coste de almacenamiento. Evite el Tipo 3 para historiales largos o requisitos de auditoría legales — en esos casos, el SCD Tipo 2 es más apropiado.
Paso 3: Definir esquema de la fact table
Crear la estructura de la fact con columnas para la clave de la fact, claves dimensionales, medidas y pares de columnas para versiones (current/previous). Incluya sellos temporales para saber cuándo ocurrió el cambio y metadatos para trazabilidad. Indexe las columnas de clave y, si es necesario, particione por date_key para consultas analíticas.
CREATE TABLE fact_sales_scd3 (
sale_id BIGINT PRIMARY KEY,
customer_key INT REFERENCES dim_customer(customer_key),
product_key INT REFERENCES dim_product(product_key),
date_key INT,
quantity INT,
price_current NUMERIC(10,2),
price_previous NUMERIC(10,2) NULL,
price_changed_date TIMESTAMP NULL,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Considerar índices: CREATE INDEX idx_fact_sales_prod_date ON fact_sales_scd3(product_key, date_key);
Paso 4: Lógica ETL para insertar nuevas ventas
Para cada nueva transacción inserte price_current con el precio vigente y price_previous a NULL (o igual a price_current si prefiere convención local). En escenarios de evento inmutable (cada venta es única) la primera inserción mantiene previous NULL. Para cargas en batch, procese en bloques (por ejemplo 10k-100k rows) y use transacciones para consistencia.
INSERT INTO fact_sales_scd3 (
sale_id, customer_key, product_key, date_key, quantity, price_current
) VALUES (
1001, 200, 300, 20230901, 2, 19.99
);
Paso 5: Lógica ETL para actualizar precio existente (migrar current → previous)
Cuando detecte que el precio del producto cambió y desee actualizar fact rows que representan posiciones continuas, actualice de forma transaccional: mover price_current a price_previous, poner el nuevo precio en price_current y registrar la fecha de cambio. Para escenarios con millones de filas afectadas, prefiera operaciones por batches y use MERGE para reducir contención.
-- Ejemplo con MERGE (PostgreSQL/SQL Server sintaxis conceptual)
BEGIN;
UPDATE fact_sales_scd3
SET price_previous = price_current,
price_current = 24.99,
price_changed_date = '2023-09-15',
last_updated = CURRENT_TIMESTAMP
WHERE product_key = 300
AND date_key <= 20230915
AND (price_current IS DISTINCT FROM 24.99);
COMMIT;
Nota: filtre para evitar actualizaciones innecesarias y registre métricas (número de filas afectadas). Para cambios masivos, planifique ventanas fuera de horario pico.
Paso 6: Tratar conflictos y datos históricos limitados
Defina la política cuando price_previous ya está relleno: sobrescribir con la versión anterior más reciente (modo sustitución), mantener una cadena corta con columnas adicionales (price_prev2, price_prev3) o rechazar la modificación para fines de auditoría. Ejemplo: mantener hasta 2 versiones anteriores reduce el 99% de los requisitos de análisis en un escenario en el que el 85% de las consultas solo necesitan 1 anterior. Documente claramente la semántica para los analistas e implemente tests que verifiquen el comportamiento en casos de múltiples cambios en 30 días.
Paso 7: Ejemplo de consulta para análisis de variación
Consultas típicas con una fact Tipo 3 comparan price_current y price_previous para calcular diferencia y porcentaje. Trate los NULLs (primera venta) utilizando COALESCE o filtros.
SELECT
p.product_key,
p.product_name,
SUM(f.quantity) AS total_qty,
AVG(f.price_current) AS avg_price_current,
AVG(f.price_previous) AS avg_price_previous,
(AVG(f.price_current) - AVG(COALESCE(f.price_previous, f.price_current))) AS avg_price_diff,
CASE WHEN AVG(f.price_previous) IS NULL THEN NULL
ELSE (AVG(f.price_current) - AVG(f.price_previous)) / AVG(f.price_previous) * 100 END AS pct_change
FROM fact_sales_scd3 f
JOIN dim_product p ON f.product_key = p.product_key
GROUP BY p.product_key, p.product_name;
Verificar el resultado
Valide que price_current y price_previous están correctos: compare muestras antes y después, ejecute conteos (por ejemplo, SELECT COUNT(*) WHERE price_previous IS NOT NULL) y valide que el número de filas afectadas corresponde a las expectativas (por ej., 50k filas actualizadas). Cree tests automatizados que simulen primera inserción, cambio único y cambio subsiguiente cuando ya existe un previous. Monitorice rendimiento y logs de ETL para detectar regresiones.
Conclusión
Una Slowly Changing Fact Table Tipo 3 permite mantener versiones limitadas de atributos dentro de la propia fact, simplificando análisis de variación sin multiplicar filas. Evalúe siempre si el Tipo 3 satisface requisitos de auditoría o si debe migrar al Tipo 2. Próximos pasos: automatizar la lógica ETL (agendamientos diarios/horarios), añadir alertas cuando el número de cambios exceda thresholds (por ejemplo >5%/día) y documentar la semántica de cada columna current y previous para evitar interpretaciones erróneas por parte de los analistas.