La idea
La granularidad (grain) de una tabla es lo que significa una fila. En orders una fila es un
pedido. En order_items una fila es una línea de un pedido. En 'Date' una fila es un día del
calendario. Antes de construir nada, debes poder terminar la frase "una fila de esta tabla es ..."
sin ningún "o".
La granularidad parece vocabulario, pero es la regla detrás de casi todas las decisiones de modelado. Un número pertenece a la tabla cuya granularidad describe, y una relación conecta tablas cuyas granularidades encajan: muchas líneas en un pedido, muchos pedidos en un día.
Figura 1
Un valor del nivel de pedido, copiado en las líneas de pedido
| order_id | shipping_fee |
|---|---|
| 1003 | 4.99 |
| 1010 | 6.95 |
| order_item_id | order_id | line_total | shipping_fee |
|---|---|---|---|
| 8 | 1003 | 83.69 | 4.99 |
| 9 | 1003 | 314.10 | 4.99 |
| 23 | 1010 | 11.99 | 6.95 |
| 24 | 1010 | 26.99 | 6.95 |
orderstiene una fila por pedido, así que su granularidad es el pedido. Cada pedido tiene una tarifa de envío: $4.99 y $6.95, $11.94 en total.- Copia la tarifa en las líneas y la granularidad cambia a una fila por línea. El pedido 1003 tiene dos líneas, así que su tarifa ahora está escrita dos veces.
SUM(shipping_fee)sobre la copia devuelve $23.88 en lugar de $11.94. Los datos no están mal. La suma cuenta una vez por línea un número que es del nivel de pedido.- En el modelo,
shipping_feese queda enordersy se suma sobre una fila por pedido. La relación lleva el filtro y ningún número se repite.
Por qué existe
Un modelo que ignora la granularidad da totales erróneos sin dar ningún error. Power BI suma encantado cualquier columna que le des. Si un valor que pertenece a un pedido está en cada línea de ese pedido, la suma lo cuenta una vez por línea. Los ingresos se inflan, el informe se ve bien y el gerente decide con un número que nadie comprobó.
Declarar la granularidad también te dice en qué tabla va una columna nueva. La tarifa de envío
describe el pedido, así que vive en orders. La quantity de una línea describe la línea, así que
vive en order_items. Cuando una columna cabe en dos tablas, es que aún no has decidido la
granularidad de una de ellas.
Cómo funciona por dentro
Nombra la granularidad y luego relaciona
Hazle la pregunta de la granularidad a cada tabla del modelo y escríbela:
| Tabla | Una fila es | Rol |
|---|---|---|
order_items |
un producto en un pedido | hechos |
orders |
un pedido | cabecera, relaciona las dimensiones |
products |
un producto | dimensión |
customers |
un cliente | dimensión |
'Date' |
un día del calendario | dimensión |
Una relación siempre une una granularidad más gruesa (el lado 1) con una más fina (el lado muchos). Una relación entre dos tablas con la misma granularidad es de uno a uno, lo que normalmente significa que deberían ser una sola tabla. Dos tablas que son "muchos" una de la otra son muchos a muchos, y eso indica que falta una dimensión entre ellas.
Qué comprueba el motor
Cuando creas una relación en Model view (Home → Manage relationships → New), Power BI comprueba que el lado 1 no tenga la clave repetida. Una clave duplicada en el supuesto lado 1 significa que la granularidad no es la que pensabas, y la relación se rechaza o se vuelve de muchos a muchos. Arregla la granularidad (quita duplicados o busca la clave real); no cambies la cardinalidad para que desaparezca el error.
Un solo camino activo
Entre dos tablas cualesquiera, el modelo permite un solo camino activo. Los filtros siguen las relaciones activas y nada más. Si existieran dos caminos, un filtro podría llegar a una tabla por las dos rutas y la respuesta dependería de cuál eligiera el motor.
Figura 2
Dos relaciones desde 'Date', una de ellas inactiva
'Date'
- Date
- Year
- Month
Year = 2025
orders
- order_id
- customer_id
- order_date
customers
- customer_id
- signup_date
- segment
order_items
- order_item_id
- order_id
- line_total
- Las ventas dependen de la fecha del pedido, así que
'Date'[Date]se relaciona conorders[order_date]. Una segmentación de Year filtra'Date', y el filtro fluye aordersyorder_items. - Marketing también quiere altas por mes, a partir de
customers[signup_date]. Es una segunda fecha, así que hace falta una segunda relación con el mismo calendario. Se dibuja punteada: inactiva. - Si estuviera activa, un filtro de Year llegaría a
orderspor dos caminos: directo y a través decustomers. Los dos podrían dar conjuntos de pedidos distintos, así que Power BI no lo permite. - Una relación inactiva duerme hasta que una medida la llama:
CALCULATE([Customers], USERELATIONSHIP(customers[signup_date], 'Date'[Date]))la activa solo para esa medida.
Por eso una segunda fecha tiene que ser inactiva. Harbor & Pine tiene dos fechas que
significan cosas distintas: cuándo se hizo un pedido y cuándo se dio de alta un cliente. Ambas se
relacionan con la misma tabla 'Date'. La relación de la fecha del pedido es activa porque las
ventas son la historia principal. La relación de la fecha de alta es inactiva, se dibuja
punteada, y una medida la activa de forma explícita con USERELATIONSHIP. En el cuadro Edit
relationship la casilla se llama Make this relationship active.
El mismo calendario juega dos papeles, lo que se llama una dimensión de rol múltiple
(role-playing dimension). Mantienes una sola tabla 'Date', con todas sus columnas, y dejas que
distintas relaciones la usen. La alternativa, una copia del calendario por cada fecha, duplica el
mantenimiento y divide las segmentaciones.
Marca el calendario
Una regla más va aquí. En una tabla 'Date', usa Table tools → Mark as date table y elige la
columna Date. Eso le dice a DAX cuál es la columna del calendario, y desbloquea la inteligencia
de tiempo de los próximos capítulos. La tabla debe tener una fila por día, sin huecos, desde la
primera fecha de pedido hasta la última.
Las jerarquías también se apoyan en la granularidad
Una jerarquía (hierarchy) es un camino ordenado por columnas de una dimensión, como Year, Quarter, Month, Date. Créala en el panel Data (clic derecho en una columna, Create hierarchy) y un gráfico puede bajar de año a mes con un clic. Solo funciona porque cada nivel encaja en el siguiente: un mes pertenece a un año. Una columna que no encaja, como el día de la semana, no pertenece a esa jerarquía. Además, los nombres de mes se ordenan alfabéticamente salvo que uses Sort by column con el número de mes.
Errores comunes
"El filtrado bidireccional arregla cualquier problema del modelo"
Una relación en Both deja que los filtros suban también del lado muchos al lado uno. Parece una cura cuando una segmentación no llega a una tabla, y a menudo es la causa del siguiente error: más caminos, visuales más lentos, ambigüedad. Antes pregúntate si la segmentación está en la tabla correcta. La estrella normalmente necesita dirección única, de las dimensiones a los hechos.
"Si un total parece razonable, la granularidad es la correcta"
Un valor repetido no rompe nada a la vista. Cada visual sigue dibujando un número; solo que es
demasiado alto. Nada lo señala. La comprobación es sumar la columna con su propia granularidad,
aquí los $11.94 de orders, y compararla con la misma medida en un visual. Si difieren, se está
contando un valor más de una vez.
"Dos relaciones entre las mismas tablas son un error"
Están bien mientras como máximo una sea activa. Que una fecha juegue varios papeles es la razón más común para tener dos. Power BI mismo crea la segunda como inactiva cuando detecta relaciones automáticamente. El error son dos caminos activos, no dos relaciones.
Ponte a prueba
Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.
Correcto. Una copia plana tiene una fila por línea, así que un pedido con dos líneas pone su tarifa en dos filas y SUM suma las dos. Los valores del nivel de pedido deben quedarse en la tabla de pedidos.
No del todo. SUM suma todas las filas que recibe. DAX solo elimina duplicados cuando se lo pides con DISTINCT o DISTINCTCOUNT, e incluso así tarifas iguales de pedidos distintos se fundirían.Prueba otra respuesta.
No del todo. No se pierde nada. Un merge copia el valor en cada línea; el problema lo causa justo esa copia.Prueba otra respuesta.
No del todo. La dirección decide cómo viajan los filtros, no cómo se cuentan las filas. Los $4.99 de más ya están en la columna antes de que corra ningún filtro.Prueba otra respuesta.
Lección hecha: ya está marcada en el mapa.