🎯

Patrones Avanzados de Modelado

Role-playing dimensions, SCD, snapshots, factless fact tables y bridge tables — los patrones que marcan la diferencia en proyectos Power BI empresariales

Avanzado
🎭

Role-Playing Dimensions

Una dimensión usada en múltiples contextos

Una role-playing dimension es cuando la misma tabla de dimensión se usa múltiples veces en el modelo, cada vez en un "rol" diferente. El caso más común es DimFecha relacionada con múltiples columnas de fecha de una tabla de hechos.

Ejemplo: tres fechas en FactVentas

// FactVentas tiene tres columnas de fecha:
FactVentas[FechaPedido]   → DimFecha (relación ACTIVA)
FactVentas[FechaEnvio]    → DimFecha (relación inactiva)
FactVentas[FechaEntrega]  → DimFecha (relación inactiva)

// Solo UNA relación puede estar activa por par de tablas.
// Las otras se activan con USERELATIONSHIP en DAX:

Ventas por Fecha Envio =
CALCULATE(
    SUM(FactVentas[Importe]),
    USERELATIONSHIP(FactVentas[FechaEnvio], DimFecha[Fecha])
)

Ventas por Fecha Entrega =
CALCULATE(
    SUM(FactVentas[Importe]),
    USERELATIONSHIP(FactVentas[FechaEntrega], DimFecha[Fecha])
)

Alternativa: copias de DimFecha

En lugar de relaciones inactivas, puedes crear copias de DimFecha con nombres distintos (DimFechaPedido, DimFechaEnvio, DimFechaEntrega) — cada una con una relación activa. Ventaja: DAX más simple, sin USERELATIONSHIP. Desventaja: el modelo ocupa más memoria con tablas duplicadas.

Slowly Changing Dimensions (SCD)

¿Qué pasa cuando los atributos de una dimensión cambian?

Los Slowly Changing Dimensions (SCD) son dimensiones cuyos atributos cambian con el tiempo. Ejemplo: un cliente cambia de región, un producto cambia de categoría, un empleado cambia de departamento. ¿Cómo tratas el histórico?

SCD Tipo 1 — Sobreescribir (el más simple)

// Simplemente actualizas el valor actual.
// No hay histórico — solo el estado actual.
DimCliente:
IDCliente | Nombre | Region
1042      | Ana    | Sur    ← era "Norte", ahora es "Sur"

// Útil cuando no importa el histórico.
// Ejemplo: corrección de errores de datos, cambios sin impacto analítico.

SCD Tipo 2 — Histórico completo (el más usado)

// Añades una nueva fila por cada cambio.
// La fila anterior cierra su vigencia y la nueva la abre.
DimCliente:
IDCliente | NaturalKey | Nombre | Region | FechaInicio | FechaFin   | EsActual
1         | 1042       | Ana    | Norte  | 2020-01-01  | 2024-03-14 | FALSE
2         | 1042       | Ana    | Sur    | 2024-03-15  | NULL       | TRUE

// Ventaja: análisis histórico correcto — las ventas de 2022 muestran
// "Ana de la región Norte" aunque ahora esté en "Sur".
// Desventaja: más complejo de cargar y mantener.

SCD Tipo 3 — Columna histórica

// Añades columnas para el valor actual y el anterior.
// Solo guarda UN nivel de histórico.
DimCliente:
IDCliente | Nombre | RegionActual | RegionAnterior
1042      | Ana    | Sur          | Norte

// Simple pero limita el histórico a un solo período anterior.
📸

Tablas de Snapshot

Foto del estado en un momento del tiempo

Un snapshot (instantánea periódica) es una tabla de hechos que registra el estado de algo en un momento específico — no un evento, sino un estado. Ejemplos: inventario a final de día, saldo de cuenta a fin de mes, pipeline de ventas semanal.

// FactInventarioSnapshot — estado del inventario cada día
IDFecha | IDProducto | IDAlmacen | UnidadesStock | ValorStock

// Diferente a FactMovimientosInventario (tabla de eventos):
// FactMovimientos registra CUÁNDO cambió el stock (evento)
// FactInventario registra CUÁNTO stock hay cada día (estado)

// Para analizar inventario "de hoy":
Stock Actual = CALCULATE(
    SUM(FactInventarioSnapshot[UnidadesStock]),
    LASTDATE(DimFecha[Fecha])  // solo el snapshot más reciente
)

// Para analizar inventario "a fin de mes":
Stock Fin Mes = CALCULATE(
    SUM(FactInventarioSnapshot[UnidadesStock]),
    LASTNONBLANK(DimFecha[Fecha], SUM(FactInventarioSnapshot[UnidadesStock]))
)
⚠️ Ojo con esto: Las tablas de snapshot crecen rápido — si tienes 10.000 productos en 500 almacenes y haces snapshot diario, son 5M de filas por día. A un año serán 1.825M de filas. Evalúa la granularidad (diaria, semanal, mensual) según tus necesidades de análisis.
🔲

Factless Fact Tables

Tablas de hechos sin métricas numéricas

Una factless fact table (tabla de hechos sin métricas) registra la ocurrencia de un evento sin ningún valor numérico. Solo tiene claves foráneas que documentan QUÉ ocurrió, CUÁNDO, QUIÉN y DÓNDE — sin un "cuánto".

Ejemplos de factless fact tables:

  • Asistencia a clase: IDFecha, IDEstudiante, IDClase — "el estudiante asistió"
  • Cobertura de producto: IDFecha, IDProducto, IDTienda — "el producto estaba disponible en la tienda"
  • Interacciones de cliente: IDFecha, IDCliente, IDCanal, IDTipoInteraccion
// Tabla de hechos de asistencia (sin métricas numéricas)
FactAsistencia:
IDFecha | IDEstudiante | IDClase | IDProfeSor

// Medida: contar eventos (la métrica se deriva del conteo)
Total Asistencias = COUNTROWS(FactAsistencia)

Tasa Asistencia =
DIVIDE(
    COUNTROWS(FactAsistencia),
    [Total Alumnos Matriculados],
    0
)
🌉

Bridge Tables (Many-to-Many)

Resolución de relaciones N:N con tabla puente

Cuando tienes una relación genuinamente Many-to-Many entre dos tablas (un cliente puede pertenecer a varios segmentos, un producto puede estar en varias categorías), la solución clásica es una tabla puente (bridge table) que descompone la relación N:N en dos relaciones 1:N.

// Problema: un cliente puede pertenecer a múltiples segmentos
DimCliente: IDCliente, Nombre
DimSegmento: IDSegmento, NombreSegmento

// Relación N:N: cliente ↔ segmento (sin tabla puente → no se puede modelar con 1:N)

// Solución: tabla puente
FactClienteSegmento:
IDCliente | IDSegmento
1042      | 1          ← Ana pertenece a "Premium"
1042      | 3          ← Ana también pertenece a "Fidelizado"
1058      | 1          ← Carlos pertenece a "Premium"

// Relaciones en el modelo:
DimCliente (1) ──► FactClienteSegmento (N)
DimSegmento (1) ──► FactClienteSegmento (N)

// DAX para contar clientes únicos por segmento (cuidado con el doble conteo)
Clientes por Segmento =
CALCULATE(
    DISTINCTCOUNT(FactClienteSegmento[IDCliente]),
    CROSSFILTER(DimCliente[IDCliente], FactClienteSegmento[IDCliente], BOTH)
)
💡 Tip kawaii: Power BI también permite relaciones N:N directas (sin tabla puente) desde la versión 2018. Sin embargo, las tablas puente ofrecen más control sobre el comportamiento del filtro y son más predecibles en DAX. Para la mayoría de los casos empresariales, la tabla puente sigue siendo la práctica recomendada. 🌸
🚀 ¡Patrones avanzados dominados! Con estos patrones en tu arsenal, estás equipado para abordar cualquier desafío de modelado empresarial. Para complementar esta sección, revisa los Errores Comunes en Modelado — conocer los errores te ahorrará horas de depuración.