Introducción
En DAX, el linaje es un concepto importante y es vital comprender cómo trabajar con él y manipularlo.
Como hice en artículos anteriores, utilizaré consultas DAX para explicar este concepto y sus efectos.
Empiezo con una consulta sencilla para obtener el recuento de pedidos del producto de la marca “Adventure Works”:
EVALUAR CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[NombreCortoMes] ,'Fecha'[ClaveMes] ,'Producto'[NombreCategoríaProducto] ,"Recuento de pedidos", [Recuento de pedidos en línea] ), 'Producto'[Nombre de marca] = "Adventure Works" ) ORDENAR POR 'Fecha'[Clave de mes] ,'Producto'[Nombre de categoría del producto]
Este es un extracto del resultado de la consulta:
Esta consulta devuelve 180 filas. Téngalo en cuenta, ya que será importante más adelante.
A continuación, introduciré un filtro para un mes específico y mostraré el papel del linaje.
Establecer el linaje
Agregaré un filtro para abril de 2026:
DEFINE VAR YearMonthFilter = 202604 EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[NombreCortoMes] ,'Fecha'[ClaveMes] ,'Producto'[NombreCategoríaProducto] ,"Recuento de pedidos", [Recuento de pedidos en línea] ), 'Producto'[Nombre de marca] = "Adventure Works" ,'Fecha'[ClaveMes] = AñoMesFilter ) ORDENAR POR 'Fecha'[ClaveMes] ,'Producto'[NombreCategoríaProducto]
En este caso, defino una variable y establezco el valor en 202604.
A continuación, lo agrego como filtro a la función CALCULATETABLE().
Nada especial hasta ahora.
Este es el resultado:
En este caso, el linaje no es importante, ya que un valor escalar establece el filtro.
Pero podemos establecer un linaje usando la función TREATAS():
DEFINE VAR YearMonthFilter = TREATAS({ 202604 }, 'Fecha'[MonthKey]) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[MonthShortName] ,'Fecha'[MonthKey] ,'Producto'[ProductCategoryName] , "Recuento de pedidos", [Recuento de pedidos en línea]) ,'Producto'[BrandName] = "Adventure Works" ,YearMonthFilter ) ORDENAR POR 'Fecha'[MonthKey] ,'Producto'[ProductCategoryName]
Como puedes ver, la introducción de TREATAS() nos permite pasar la variable como filtro. CALCULATETABLE() utiliza el linaje establecido por TREATAS() como filtro en la columna 'Fecha'[MesClave].
El resultado no cambia, pero la consulta es más sencilla, ya que no necesito pasar la condición "columna es igual al valor del filtro".
De hecho, Power BI usa este formulario todo el tiempo cuando pasa los filtros establecidos en un informe al modelo semántico.
Pero lo hace de manera diferente:
Define variables, establece el linaje y agrega todos los filtros directamente a SUMMARIZECOLUMNS():
DEFINE VAR YearMonthFilter = TREATAS({ 202604 }, 'Fecha'[MonthKey]) VAR SelectedBrand = TREATAS( { "Adventure Works" }, 'Producto'[Marca]) EVALUATE SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[MesNombreCorto] ,'Fecha'[MesClave] ,'Producto'[NombreCategoríaProducto],FiltroMesAño,MarcaSeleccionada,"Recuento de pedidos", [Recuento de pedidos en línea]) ORDENAR POR 'Fecha'[ClaveMes],'Producto'[NombreCategoríaProducto]
Limpiando el linaje
Es posible que se encuentre con situaciones en las que necesite limpiar el linaje.
El método para hacerlo varía dependiendo de si tiene uno o varios valores como filtro.
Por ejemplo, mire el siguiente código, donde uso VALUE() para eliminar el linaje de la expresión anterior:
DEFINE VAR YearMonthFilter = TREATAS({ 202604 }, 'Fecha'[MonthKey]) VAR YearMonthFilter_cleared = VALUE(YearMonthFilter) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[MonthShortName] ,'Fecha'[MonthKey] ,'Product'[ProductCategoryName] ,"Recuento de pedidos", [Recuento de pedidos en línea] ) ,'Product'[BrandName] = "Adventure Works" ,YearMonthFilter_cleared ) ORDENAR POR 'Fecha'[MonthKey] ,'Producto'[ProductCategoryName]
Este es el error entregado por Power BI:
El motor no puede funcionar con el filtro en la línea 71 porque ya no tiene linaje.
Funcionará de esta forma:
DEFINE VAR YearMonthFilter = TREATAS({ 202604 }, 'Fecha'[MonthKey]) VAR YearMonthFilter_cleared = VALUE(YearMonthFilter) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[MonthShortName] ,'Fecha'[MonthKey] ,'Product'[ProductCategoryName] ,"Recuento de pedidos", [Recuento de pedidos en línea] ) ,'Product'[BrandName] = "Adventure Works" ,'Date'[MonthKey] = YearMonthFilter_cleared ) ORDENAR POR 'Fecha'[MonthKey] ,'Producto'[ProductCategoryName]
Como puede ver aquí, la consulta devuelve el mismo resultado que antes:
Observe el cambio del argumento del filtro en la línea 91.
Pero existe una forma más sencilla de borrar el linaje cuando se trabaja con medidas.
Mire la siguiente consulta con la Medida [Recuento de pedidos año completo], que calcula el recuento de pedidos para todo el año:
DEFINE MEASURE 'Todas las medidas'[Recuento de pedidos año completo] = VAR SelYear = TREATAS({ SELECTEDVALUE('Fecha'[Año]) }, 'Fecha'[Año]) RETURN CALCULATE([Recuento de pedidos en línea] ,REMOVEFILTERS('Fecha') ,SelYear ) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año],'Fecha'[NombreCortoMes],'Fecha'[ClaveMes],'Producto'[NombreCategoríaProducto],"Recuento de pedidos", [Recuento de pedidos en línea], "Recuento de pedidos año completo", [Recuento de pedidos año completo]), 'Producto'[Marca] = "Adventure Works" ) ORDER BY 'Fecha'[ClaveMes] ,'Producto'[Nombre de categoría del producto]
Este es un extracto del resultado:
Ahora agrego un valor escalar a la variable:
DEFINE MEASURE 'Todas las medidas'[Recuento de pedidos año completo] = VAR SelYear = TREATAS({ SELECTEDVALUE('Fecha'[Año]) }, 'Fecha'[Año]) VAR SelYear_Plus1 = SelYear + 0 RETURN CALCULATE([Recuento de pedidos en línea], REMOVEFILTERS('Fecha'), SelYear_Plus1) EVALUAR CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[NombreCortoMes] ,'Fecha'[ClaveMes] ,'Producto'[NombreCategoríaProducto] ,"Recuento de pedidos", [Recuento de pedidos en línea] ,"Recuento de pedidos año completo", [Recuento de pedidos año completo] ), 'Producto'[Nombre de marca] = "Adventure Works" ) ORDER BY 'Fecha'[ClaveMes] ,'Producto'[NombreCategoríaProducto]
Esta operación borra el linaje y la medida ya no funciona:
Lo que todavía puedo hacer es usar un filtro igual para obtener el resultado anterior:
DEFINE MEASURE 'Todas las medidas'[Recuento de pedidos año completo] = VAR SelYear = TREATAS({ SELECTEDVALUE('Fecha'[Año]) }, 'Fecha'[Año]) VAR SelYear_Plus1 = SelYear + 0 RETURN CALCULATE([Recuento de pedidos en línea], REMOVEFILTERS('Fecha'), 'Fecha'[Año] = SelYear_Plus1 ) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[NombreCortoMes] ,'Fecha'[ClaveMes] ,'Producto'[NombreCategoríaProducto] ,"Recuento de pedidos", [Recuento de pedidos en línea] ,"Recuento de pedidos año completo", [Recuento de pedidos año completo]) ,'Product'[BrandName] = "Aventura Trabajos") ORDENAR POR 'Fecha'[ClavedelMes],'Producto'[NombreCategoríaProducto]
Ahora obtengo el mismo resultado que antes:
Y ahora, usemos múltiples valores como filtro.
Por ejemplo, dos meses:
DEFINE VAR YearMonthFilter = TREATAS({ 202604, 202605 }, 'Fecha'[MonthKey]) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[MonthShortName] ,'Fecha'[MonthKey] ,'Producto'[ProductCategoryName] ,"Recuento de pedidos", [En línea Recuento de pedidos]), 'Producto'[BrandName] = "Adventure Works", YearMonthFilter) ORDENAR POR 'Fecha'[MonthKey],'Producto'[ProductCategoryName]
Aquí está el resultado de esta consulta:
Una forma de eliminar el linaje de una variable con múltiples valores es usar SUMMARIZECOLUMNS():
DEFINE VAR YearMonthFilter = TREATAS({ 202604, 202605 }, 'Fecha'[MonthKey]) VAR YearMonthFilter_cleared = SUMMARIZECOLUMNS(YearMonthFilter) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año], 'Fecha'[MonthShortName] ,'Fecha'[MonthKey] ,'Product'[ProductCategoryName] ,"Recuento de pedidos", [Recuento de pedidos en línea] ) ,'Product'[BrandName] = "Adventure Works" ,YearMonthFilter_cleared ) ORDENAR POR 'Fecha'[MonthKey] ,'Producto'[ProductCategoryName]
Desafortunadamente, este método elimina el filtro por completo y todos los meses se devuelven en 180 filas (el mismo número de filas que en la consulta inicial):
Técnicamente, el linaje no se borra porque la consulta aún funciona, pero se elimina el filtro mensual.
Pero cuando intentas usar la variable "YearMonthFilter_cleared" con un operador IN, ya no funciona:
En este contexto, probé otras funciones, como DISTINCT() y VALUES(). Mientras que DISTINCT() no tuvo ningún efecto, VALUES() sí.
Por ejemplo, mientras esta consulta no funciona:
DEFINE VAR YearMonthFilter = TREATAS({ 202604, 202605 }, 'Fecha'[MonthKey]) VAR YearMonthFilter_cleared = VALUES(YearMonthFilter) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[MonthShortName] ,'Fecha'[MonthKey] ,'Product'[ProductCategoryName] ,"Recuento de pedidos", [Recuento de pedidos en línea] ) ,'Product'[BrandName] = "Adventure Works" ,YearMonthFilter_cleared ) ORDENAR POR 'Fecha'[MonthKey] ,'Producto'[ProductCategoryName]
Aquí está el mensaje de error:
Esto funciona cuando se usa un operador IN, que indica que el linaje se borra cuando se usa VALUES():
DEFINE VAR YearMonthFilter = TREATAS({ 202604, 202605 }, 'Fecha'[MonthKey]) VAR YearMonthFilter_cleared = VALUES(YearMonthFilter) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[MonthShortName] ,'Fecha'[MonthKey] ,'Product'[ProductCategoryName] ,"Recuento de pedidos", [Recuento de pedidos en línea] ) ,'Product'[BrandName] = "Adventure Works" ,'Date'[MonthKey] IN YearMonthFilter ) ORDEN POR 'Fecha'[MonthKey] ,'Producto'[ProductCategoryName]
Aquí está el resultado de la consulta:
La documentación de VALUES() indica que esta función requiere una referencia de tabla o columna.
Pero este comportamiento muestra que depende de cómo se usa la variable en la consulta.
Como la variable tiene un linaje establecido, VALUES() la acepta como referencia de columna.
Manipular el linaje
A continuación, cambiemos la forma en que aplicamos un filtro manipulando el linaje.
Quiero crear un informe que muestre todos los pedidos en línea por país, junto con los pedidos atendidos por las tiendas en cada país.
Por ejemplo, tengo 68 pedidos de clientes de Alemania en abril de 2026. Quiero ver cuántos pedidos han atendido las tiendas de ese país, si corresponde.
Algo como esto:
Puedo hacerlo trabajando con una variable:
DEFINE MEASURE 'Todas las medidas'[Pedidos servidos desde el país] = VAR SelCountry = SELECTEDVALUE('Cliente'[RegiónPaísNombre]) RETURN CALCULATE([Recuento de pedidos en línea], REMOVEFILTERS(Cliente[RegiónPaísNombre]) ,'Tienda'[RegiónPaísNombre] = SelPaís) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año],'Fecha'[Mes],'Fecha'[MesClave],'Cliente'[NombrePaísRegión],"Recuento de pedidos", [Recuento de pedidos en línea], "Verificación del país del recuento de pedidos", [Verificación del país del recuento de pedidos]), 'Producto'[Nombre de la marca] = "Adventure Works" ) ORDER BY 'Fecha'[Año] ,'Fecha'[Mes] ,'Cliente'[NombrePaísRegión]
En este enfoque, almaceno el país actual en una variable. Luego elimino el filtro de Cliente[NombrePaísRegión]. Y lo reemplazo con un filtro en 'Tienda'[NombrePaísRegión].
Este es el resultado:
O puedo hacerlo de esta manera usando TREATAS():
DEFINE MEASURE 'Todas las medidas'[Pedidos servidos desde el país] = CALCULATE([Recuento de pedidos en línea] ,REMOVEFILTERS(Cliente[RegiónPaísNombre]) ,TREATAS(VALUES('Cliente'[RegiónPaísNombre]) ,'Tienda'[RegiónPaísNombre]) ) EVALUATE CALCULATETABLE( SUMMARIZECOLUMNS('Fecha'[Año] ,'Fecha'[Mes] ,'Fecha'[ClavedelMes] ,'Cliente'[NombrePaísRegión] ,"Recuento de pedidos", [Recuento de pedidos en línea] ,"Pedidos atendidos desde el país", [Pedidos atendidos desde el país] ), 'Producto'[Nombre de marca] = "Adventure Works" ) ORDENAR POR 'Fecha'[Año] ,'Fecha'[Mes] ,'Cliente'[NombrePaísRegión]
En este enfoque, elimino nuevamente el filtro de Cliente[NombrePaísRegión]. Pero luego uso TREATAS() para cambiar el linaje del filtro del país actual a 'Tienda'[NombrePaísRegión].
En este enfoque, no necesito una variable; Puedo filtrar directamente la tabla de tiendas por el país actual.
Este código es mucho más corto pero puede resultar más difícil de entender para los lectores que no saben cómo funciona TREATAS().
Trabajar con tablas
Dado que podemos crear tablas ad hoc en DAX, podríamos tener problemas porque esas tablas carecen de linaje.
Puedo empezar a escribir código sobre esto, pero SQLBI ya lo hizo y puedes leer su artículo, que está muy bien explicado:
Puedes encontrarlo aquí:
https://www.sqlbi.com/articles/understanding-data-lineage-in-dax
Conclusión
El concepto de linaje puede ser difícil de entender, aunque lo usamos todo el tiempo cuando trabajamos con filtros en DAX.
Power BI genera código usando TREATAS() todo el tiempo cuando aplica filtros de informes.
Y a veces puede conducir a un código DAX más simple cuando se sabe cómo manipularlo de manera eficiente.
Esto puede resultar vital cuando creo una tabla con DAX a partir de tablas existentes. La mesa conservará el linaje. Esto puede generar problemas cuando intento agregar relaciones a las tablas de origen. Sin borrar el linaje, encontraré un error debido a una dependencia circular.
Le animo a que comience a experimentar con los conceptos que se muestran aquí y a intentar optimizar su código DAX.
Aunque "optimizar" es la palabra incorrecta, ya que no noté ninguna mejora en el rendimiento al usar las variantes mostradas.
Pero tener un código DAX que sea más corto y más fácil de leer puede ser una optimización en sí mismo.
Diviértete trabajando con él.
Referencias
Un artículo de SQLBI sobre cómo trabajar con linaje:
https://www.sqlbi.com/articles/understanding-data-lineage-in-dax
Como en mis artículos anteriores, utilizo el conjunto de datos de muestra de Contoso. Puede descargar el conjunto de datos ContosoRetailDW de forma gratuita desde Microsoft aquí.
Los datos de Contoso se pueden utilizar libremente bajo la licencia MIT, como se describe en este documento. Cambié el conjunto de datos para cambiar los datos a fechas contemporáneas.