USERELATIONSHIP: relaciones inactivas sin duplicar tablas de fechas
En cualquier modelo de datos de ventas, logística o gestión de proyectos, es habitual encontrarse con una tabla de hechos que contiene múltiples columnas de fecha. Un ejemplo clásico en el sector retail es la tabla de Ventas, donde conviven la FechaPedido, la FechaEnvio y la FechaEntrega. El reto para el consultor de Business Intelligence es decidir cómo relacionar estas columnas con una única tabla de Calendario.
Si intentamos conectar las tres columnas directamente a la dimensión de fechas, Power BI solo nos permitirá tener una relación activa a la vez. El resto quedarán marcadas con una línea discontinua: son relaciones inactivas. Aquí surge la duda recurrente: ¿Duplicamos la tabla de calendario para cada fecha (técnica conocida como Role-Playing Dimensions) o utilizamos la función de DAX USERELATIONSHIP para activar las relaciones bajo demanda? Esta decisión no es estética, afecta directamente a la mantenibilidad del código, al tamaño del modelo en memoria y a la experiencia del usuario final al filtrar datos.
En este artículo analizaremos por qué, desde una perspectiva de arquitectura de datos eficiente, la opción de mantener una única tabla de dimensiones y gestionar la lógica mediante DAX suele ser la ganadora, aunque conlleva ciertos trade-offs que debemos conocer antes de desplegar la solución en producción.
Dimensiones Role-Playing vs. USERELATIONSHIP
El concepto de dimensiones Role-Playing proviene del modelado dimensional tradicional de Kimball. Consiste en tener copias físicas o vistas de la misma dimensión para diferentes propósitos. En Power BI, esto se traduce en importar la tabla Calendario tres veces y renombrarlas como Calendario Pedido, Calendario Envío y Calendario Entrega. Cada una tendrá su propia relación activa con la tabla de hechos.
Esta aproximación tiene una ventaja clara: el usuario puede arrastrar el campo ‘Mes’ de la tabla Calendario Envío y ‘Mes’ de Calendario Pedido a la misma página del informe para filtrar de forma independiente. Sin embargo, genera un desorden visual considerable en la vista de modelo y obliga a replicar toda la lógica de inteligencia de tiempo en cada una de las tablas. Además, si tu calendario ocupa un espacio significativo (aunque las fechas son ligeras, en modelos masivos cada columna cuenta), estarás desperdiciando recursos del motor VertiPaq.
Por otro lado, USERELATIONSHIP permite mantener un modelo en estrella limpio. Al aplicar técnicas para crear tablas de dimensión eficientes, lo ideal es que cada entidad del negocio esté representada una sola vez. Con la función USERELATIONSHIP, le indicamos al motor de DAX que, exclusivamente para el cálculo de una medida concreta, debe ignorar la relación activa actual y utilizar una de las inactivas.
Implementación técnica de USERELATIONSHIP
La función USERELATIONSHIP solo se puede utilizar como argumento de filtro dentro de una función CALCULATE o CALCULATETABLE. No devuelve un valor, sino que modifica el linaje de los datos durante la evaluación de la expresión. Es fundamental entender que para que funcione, la relación debe existir previamente en el modelo, aunque esté inactiva.
Supongamos que tenemos una relación activa entre Ventas[FechaPedido] y Calendario[Fecha], y una relación inactiva entre Ventas[FechaEntrega] y Calendario[Fecha]. El código para calcular el importe entregado sería el siguiente:
Importe Entregado =
CALCULATE(
[Importe Ventas], -- Reutilizamos la medida base
USERELATIONSHIP(Ventas[FechaEntrega], Calendario[Fecha]) -- Activamos la relación específica
)En este bloque de código, [Importe Ventas] es una medida simple de SUM(Ventas[Importe]). Al envolverla en un CALCULATE con USERELATIONSHIP, el motor de Power BI cambia el contexto de filtro. Si el usuario selecciona «Enero 2024» en un segmentador basado en la tabla Calendario, la medida [Importe Ventas] mostrará los pedidos realizados en enero, mientras que [Importe Entregado] mostrará las ventas cuya entrega se produjo en enero, independientemente de cuándo se hizo el pedido.
Es importante recordar que el contexto de evaluación en Power BI determina cómo se propagan los filtros. Si no usamos esta función, cualquier filtro sobre la tabla de calendario siempre afectará a la columna de la relación activa.
Impacto en el rendimiento y ambigüedad del modelo
Desde el punto de vista del rendimiento, USERELATIONSHIP es extremadamente eficiente. No añade una carga computacional significativa comparada con una relación activa estándar. El motor VertiPaq simplemente recorre un árbol de relaciones distinto. El verdadero coste es el mantenimiento del código DAX: si tienes 10 medidas básicas y 3 fechas diferentes, terminarás con 30 medidas en tu modelo para cubrir todas las combinaciones.
Un error común que he visto en auditorías de proyectos es intentar activar relaciones que generan ambigüedad. Si tienes un camino activo entre dos tablas a través de una tabla intermedia, y tratas de activar una relación directa inactiva, DAX podría devolver un error o resultados inesperados si no se gestiona correctamente el flujo del filtro. La regla de oro es: solo puede haber un camino activo entre dos tablas en un momento dado. USERELATIONSHIP desactiva temporalmente el camino predeterminado para abrir el nuevo.
Además, hay que tener especial cuidado con el contexto de fila y las relaciones inactivas. Si intentas usar estas relaciones dentro de una columna calculada o un iterador como SUMX sin invocar un CALCULATE que realice la transición de contexto, la relación inactiva simplemente no se usará. Las relaciones inactivas son invisibles para cualquier función que no sea CALCULATE.
Comparativa: ¿Cuándo elegir cada método?
No hay una respuesta única, pero la siguiente tabla resume los criterios de decisión que utilizo en mis consultorías para decidir entre duplicar tablas o usar DAX:
| Criterio | Dimensiones Role-Playing | USERELATIONSHIP (DAX) |
|---|---|---|
| Usabilidad (UX) | Alta: Filtros independientes para cada fecha. | Media: Requiere medidas específicas para cada fecha. |
| Mantenimiento | Complejo: Debes replicar lógica en N tablas. | Medio: Proliferación de medidas en el panel. |
| Tamaño del modelo | Aumenta según el número de copias. | Óptimo: Una sola tabla de dimensiones. |
| Inteligencia de Tiempo | Requiere medidas por cada tabla. | Funciona unificado sobre un solo calendario. |
Si el usuario final necesita filtrar simultáneamente por pedidos de «Lunes» y entregas de «Viernes» en el mismo gráfico, las Role-Playing Dimensions son obligatorias. Si el objetivo es comparar el volumen de pedidos vs entregas en una línea de tiempo continua, USERELATIONSHIP es la opción técnica superior.
Escenarios avanzados: Encadenamiento y CALCULATETABLE
En modelos más complejos, podrías necesitar activar una relación inactiva para filtrar no solo una medida, sino todo un conjunto de datos antes de realizar un cálculo posterior. Aquí es donde entra en juego el orden de evaluación con la función CALCULATETABLE.
Por ejemplo, si necesitas obtener una lista de clientes que solo han tenido entregas en una región específica, activando la relación de fecha de entrega, el código se volvería más sofisticado:
Clientes Con Entregas =
VAR ClientesEntregados =
CALCULATETABLE(
DISTINCT(Ventas[ClienteID]),
USERELATIONSHIP(Ventas[FechaEntrega], Calendario[Fecha])
)
RETURN
COUNTROWS(ClientesEntregados)Este patrón es muy potente porque permite «redireccionar» todo el modelo hacia la fecha de entrega para cualquier cálculo que realicemos dentro de esa variable. Es una forma limpia de trabajar con subconjuntos de datos condicionados por una relación no principal.
Errores frecuentes que debes evitar
- Olvidar crear la relación: Parece obvio, pero
USERELATIONSHIPfallará si no has arrastrado previamente los campos en la vista de modelo para crear la línea discontinua. - Incompatibilidad de tipos: Las columnas relacionadas deben tener exactamente el mismo tipo de datos (por ejemplo, ambas
DateTimeo ambasDate). - Uso en RLS: Las reglas de Seguridad a Nivel de Fila (RLS) pueden comportarse de forma errática con relaciones inactivas si no se definen con precisión los sentidos del filtro.
- Ambigüedad por relaciones bidireccionales: Si usas filtros cruzados en ambas direcciones, el uso de
USERELATIONSHIPpuede verse bloqueado por el motor para evitar ciclos lógicos. Mantén siempre relaciones de uno a muchos y dirección única si es posible.
Como consultor, mi recomendación es siempre empezar con USERELATIONSHIP. Mantiene el esquema en estrella puro y facilita enormemente el linaje de datos. Solo recurro a duplicar tablas de dimensiones cuando el requerimiento de negocio exige filtros cruzados complejos que DAX no puede resolver de forma intuitiva para el usuario final.
Preguntas frecuentes
¿Puedo usar USERELATIONSHIP con más de dos tablas?
Sí, la función acepta dos columnas como parámetros que deben pertenecer a tablas ya relacionadas en el modelo. No crea relaciones nuevas «al vuelo», simplemente activa caminos preexistentes definidos en la estructura del dataset.
¿Qué sucede si activo una relación inactiva en una medida y luego uso esa medida en otra?
La activación de la relación solo persiste durante la evaluación del CALCULATE donde se definió. Si la medida resultante se usa en un cálculo externo sin CALCULATE, volverá a regirse por la relación activa predeterminada del modelo.
¿Es posible activar dos USERELATIONSHIP en la misma medida?
Es técnicamente posible si las relaciones no entran en conflicto directo (por ejemplo, activando una relación entre Ventas y Calendario, y otra entre Ventas y Geografía). Sin embargo, no puedes activar dos relaciones distintas entre las mismas dos tablas simultáneamente.
Checklist para implementar relaciones inactivas
- Confirma que las columnas de fecha en la tabla de hechos tienen la misma granularidad que la tabla de calendario.
- Crea la relación en la vista de modelo y asegúrate de que esté configurada como ‘Inactiva’.
- Define una medida base (ej. [Total Importe]) para evitar repetir la lógica de agregación.
- Encapsula la activación en medidas específicas usando
CALCULATEyUSERELATIONSHIP. - Verifica con el Analizador de Rendimiento que no hay cuellos de botella por la complejidad de los filtros.

