| |

Conceptos avanzados de DAX: Row Context, Filter Context, Iteradores y más

0
(0)

Este artículo recorre, con ejemplos prácticos, los conceptos que más confusión generan al aprender DAX en Power BI / Power Pivot: contexto de fila, contexto de filtro, transición de contexto, iteradores, medidas evaluadas dentro de iteradores, las funciones FILTER, SUMX, AVERAGEX, MAXX, ADDCOLUMNS, y el fenómeno de CallbackDataID en escenarios de múltiples evaluaciones.

Supondremos una tabla Ventas con columnas Producto, Cantidad, PrecioUnitario, Fecha, IDCliente, y una tabla Clientes relacionada por IDCliente.


1. Row Context (Contexto de fila)

El contexto de fila existe cuando DAX evalúa una expresión fila por fila. Se genera automáticamente en columnas calculadas y dentro de iteradores.

Ejemplo — columna calculada:

Ventas[Importe] = Ventas[Cantidad] * Ventas[PrecioUnitario]

Aquí, para cada fila de Ventas, DAX «sabe» qué valores tienen Cantidad y PrecioUnitario en esa fila concreta. Eso es el contexto de fila: no hay filtro aplicado, solo un puntero a la fila actual.

Punto clave para el artículo: una medida (Ventas[Importe] :=) no tiene contexto de fila por defecto. Si escribes = Ventas[Cantidad] * Ventas[PrecioUnitario] como medida, DAX no sabe a qué fila te refieres y lanza error, salvo que uses un iterador o una transición de contexto.


2. Filter Context (Contexto de filtro)

El contexto de filtro es el conjunto de filtros activos (por segmentaciones, filas/columnas de una tabla dinámica, CALCULATE, relaciones, etc.) que determina qué subconjunto de datos ve una medida al evaluarse.

Ejemplo:

Total Ventas = SUM(Ventas[Importe])

Si esta medida está en una matriz con Producto en filas, cada celda tiene un contexto de filtro distinto: «solo las filas de Ventas donde Producto = X». SUM no suma toda la tabla, suma lo que ese contexto de filtro deja visible.

Ejemplo con CALCULATE modificando el contexto de filtro:

Ventas Producto A =
CALCULATE(
    [Total Ventas],
    Ventas[Producto] = "Producto A"
)

Aquí forzamos un filtro adicional, sin importar qué segmentación tenga el usuario activada (salvo que haya otro filtro sobre Producto que entre en conflicto y lo sobrescriba).


3. Context Transition (Transición de contexto)

Este es probablemente el mecanismo más importante —y más malentendido— de todo DAX. Merece una explicación pausada porque de él dependen casi todos los «comportamientos raros» que se encuentran quienes empiezan con el lenguaje.

La idea central

DAX tiene dos tipos de contexto que determinan qué datos ve una expresión al evaluarse, y no se comunican automáticamente entre sí:

  • Contexto de fila: existe cuando DAX está posicionado en una fila concreta (columnas calculadas, o dentro de un iterador).
  • Contexto de filtro: es el conjunto de filtros que restringen qué filas son visibles (segmentaciones, filas de una matriz, CALCULATE, relaciones activas…).

Una función de agregación como SUM solo entiende contexto de filtro; ignora por completo el contexto de fila. Por eso, si escribimos esto en una columna calculada de la tabla Clientes:

Clientes[TotalCompras] = SUM(Ventas[Importe])

el resultado es idéntico en todas las filas de Clientes: la suma total de toda la tabla Ventas. El contexto de fila (estar en la fila del cliente X) existe, pero SUM lo ignora porque no tiene forma de «traducirlo» a un filtro.

Qué hace exactamente la transición de contexto

Cuando envolvemos esa misma expresión en CALCULATE:

Clientes[TotalCompras] = CALCULATE(SUM(Ventas[Importe]))

CALCULATE hace algo muy concreto: toma todos los valores de las columnas de la fila actual y los convierte en filtros equivalentes. Es como si, mentalmente, DAX ejecutara:

CALCULATE(
    SUM(Ventas[Importe]),
    Clientes[IDCliente] = "el valor de IDCliente en esta fila",
    Clientes[Nombre] = "el valor de Nombre en esta fila"
    -- ...y así con cada columna de Clientes
)

Y como Clientes está relacionado con Ventas por IDCliente, ese filtro se propaga a través de la relación, y SUM(Ventas[Importe]) termina sumando solo las ventas de ese cliente concreto.

Esto es la transición de contexto: convertir un contexto de fila en un contexto de filtro equivalente, usando CALCULATE (o CALCULATETABLE) como disparador.

Por qué las medidas la activan sin que lo pidas explícitamente

Un detalle que sorprende a mucha gente: toda medida referenciada dentro de un contexto de fila lleva un CALCULATE implícito envolviéndola, aunque no lo escribas.

Total Ventas = SUM(Ventas[Importe])   -- esto es una medida

Clientes[Compras] = [Total Ventas]    -- columna calculada llamando a la medida

Aunque aquí no aparece ningún CALCULATE explícito, DAX lo añade internamente porque las medidas siempre se comportan como si estuvieran envueltas en CALCULATE. Por eso [Total Ventas] sí filtra correctamente por cliente, mientras que escribir SUM(Ventas[Importe]) directamente en la columna no lo hace.

Ejemplo comparando con y sin transición

-- Sin CALCULATE: no hay transición, suma TODO Ventas en cada fila de Clientes
Clientes[Incorrecto] = SUM(Ventas[Importe])

-- Con CALCULATE: transición de contexto, suma solo las ventas del cliente de esa fila
Clientes[Correcto] = CALCULATE(SUM(Ventas[Importe]))

Dónde aparece (los tres lugares típicos)

  1. Columnas calculadas que llaman a una medida o usan CALCULATE.
  2. Iteradores (SUMX, AVERAGEX, FILTER…) cuando dentro de ellos se invoca una medida:
Margen Promedio =
AVERAGEX(
    Ventas,
    [Total Ventas] - Ventas[Costo]
)

Por cada fila que recorre AVERAGEX, al llamar a [Total Ventas] se dispara una transición de contexto: se filtra Ventas según los valores de esa fila concreta antes de calcular la medida.

  1. Medidas anidadas dentro de otras medidas que usan CALCULATE.

El «efecto sorpresa»: cuando la transición sale cara

Imagina que Ventas tiene una fila por transacción y haces:

Resultado = SUMX(Ventas, [Total Ventas])

Uno podría esperar que esto simplemente sume «el total de ventas» de forma redundante. Pero lo que ocurre realmente es: por cada fila de Ventas, CALCULATE toma todas las columnas de esa fila (fecha, producto, cliente, cantidad, precio…) y filtra Ventas para que coincida exactamente con esos valores. Si no hay dos filas idénticas, el filtro deja visible solo esa fila, y [Total Ventas] devuelve el importe de esa única fila. El resultado final es equivalente a sumar la columna Importe directamente, pero con un coste de cálculo mucho mayor, porque en cada iteración se ejecuta un CALCULATE completo. Este es precisamente el tipo de escenario que puede generar los CallbackDataID que veremos en la última sección.

Regla práctica para recordar

Contexto de fila → (CALCULATE) → Contexto de filtro

Cada vez que aparece CALCULATE —explícito o implícito por el uso de una medida— dentro de algo que tiene contexto de fila, ahí está ocurriendo una transición de contexto. Es la clave para entender por qué ciertas fórmulas «funcionan solas» en columnas calculadas o dentro de iteradores sin que parezca haber ningún filtro escrito explícitamente.


4. Iteradores

Los iteradores (funciones terminadas en X) recorren una tabla fila por fila, generan contexto de fila para cada una, evalúan una expresión, y luego agregan el resultado.

Ejemplo básico:

Importe Total (con iterador) =
SUMX(
    Ventas,
    Ventas[Cantidad] * Ventas[PrecioUnitario]
)

A diferencia de SUM(Ventas[Importe]), aquí no necesitas que exista la columna Importe: SUMX la calcula «al vuelo» fila por fila y luego suma los resultados.

Por qué importa: los iteradores permiten cálculos que dependen de más de una columna combinadas de forma no lineal (por ejemplo, aplicar un descuento condicional por fila antes de sumar), algo que una simple SUM no puede hacer.


5. Medidas evaluadas dentro de iteradores

Cuando dentro de un iterador se invoca una medida (no una columna), ocurre automáticamente una transición de contexto en cada fila.

Ejemplo:

Margen Promedio =
AVERAGEX(
    Ventas,
    [Total Ventas] - Ventas[Costo]
)

En cada fila de Ventas que recorre AVERAGEX, al llamar a [Total Ventas] (una medida basada en CALCULATE/SUM), DAX aplica transición de contexto: filtra Ventas para que coincida únicamente con los valores de esa fila (por ejemplo, mismo Producto, misma Fecha, etc., según el grano de la tabla). Si Ventas no tiene una clave única por fila, esto puede devolver el mismo valor que una columna, lo cual sorprende a quien no conoce la transición de contexto.

Ejemplo de «trampa» común a mencionar en el artículo:

-- Puede ser lento o dar resultados inesperados si Total Ventas
-- ya implica su propio CALCULATE y Ventas tiene millones de filas
Resultado =
SUMX(
    Ventas,
    [Total Ventas]
)

Aquí, por cada fila de Ventas, se dispara un CALCULATE completo (transición de contexto), lo que puede ser muy costoso en rendimiento si la tabla es grande. Es un buen punto para hablar de optimización.


6. Funciones clave: FILTER, SUMX, AVERAGEX, MAXX, ADDCOLUMNS

FILTER — devuelve una tabla con las filas que cumplen una condición; casi siempre se usa dentro de CALCULATE o de otro iterador.

Ventas Grandes =
CALCULATE(
    SUM(Ventas[Importe]),
    FILTER(Ventas, Ventas[Importe] > 1000)
)

SUMX — itera y suma una expresión evaluada por fila.

Total con Impuesto =
SUMX(
    Ventas,
    Ventas[Importe] * 1.21
)

AVERAGEX — itera y calcula el promedio de una expresión por fila.

Precio Medio Ponderado =
AVERAGEX(
    Ventas,
    Ventas[Importe] / Ventas[Cantidad]
)

MAXX — itera y devuelve el máximo de una expresión evaluada por fila (útil cuando el máximo depende de una fórmula, no de una sola columna).

Mejor Margen Producto =
MAXX(
    VALUES(Ventas[Producto]),
    CALCULATE(SUM(Ventas[Importe]) - SUM(Ventas[Costo]))
)

ADDCOLUMNS — construye una tabla virtual añadiendo columnas calculadas, muy útil combinada con otras funciones para crear tablas intermedias.

Top Cliente =
MAXX(
    ADDCOLUMNS(
        VALUES(Clientes[IDCliente]),
        "GastoCliente", CALCULATE(SUM(Ventas[Importe]))
    ),
    [GastoCliente]
)

Aquí ADDCOLUMNS crea una tabla temporal con una columna GastoCliente calculada para cada cliente, y MAXX la recorre para quedarse con el valor más alto.


7. Múltiples evaluaciones y CallbackDataID

En escenarios con iteradores anidados o medidas complejas dentro de SUMX/FILTER, el motor de fórmulas (Formula Engine) a veces no puede delegar todo el cálculo al motor de almacenamiento (Storage Engine, VertiPaq), y genera llamadas de tipo CallbackDataID, visibles en el Query Plan de DAX Studio.

Qué significa: cada fila del contexto de fila «llama de vuelta» al Formula Engine para resolver parte del cálculo (por ejemplo, una medida con lógica condicional o CALCULATE complejo), en lugar de resolverlo todo en bloque en el Storage Engine. Esto puede multiplicar el número de evaluaciones y degradar el rendimiento en tablas grandes.

Ejemplo típico que genera CallbackDataID:

Ventas Filtradas =
SUMX(
    Ventas,
    IF(
        Ventas[Cantidad] > CALCULATE(AVERAGE(Ventas[Cantidad])),
        Ventas[Importe],
        0
    )
)

Aquí, por cada fila de Ventas, se recalcula un CALCULATE(AVERAGE(...)), lo que puede disparar múltiples evaluaciones y callbacks al Formula Engine.

Ejemplo de cómo mitigarlo (buena práctica a incluir en el artículo):

-- Calcular el promedio UNA vez fuera del iterador, en una variable
Ventas Filtradas Optimizada =
VAR PromedioCantidad = CALCULATE(AVERAGE(Ventas[Cantidad]))
RETURN
    SUMX(
        Ventas,
        IF(Ventas[Cantidad] > PromedioCantidad, Ventas[Importe], 0)
    )

Al usar VAR, el promedio se calcula una sola vez, y el iterador solo compara un valor constante fila por fila, evitando evaluaciones redundantes y reduciendo (o eliminando) los CallbackDataID en el plan de consulta.


Resumen para cerrar el artículo

ConceptoIdea centralEjemplo típico
Row ContextEvaluación fila a filaColumnas calculadas
Filter ContextFiltros activos al evaluarMedidas en matrices/segmentaciones
Context TransitionFila → filtro vía CALCULATEMedida usada dentro de columna calculada o iterador
IteradoresRecorren tabla fila por filaSUMX, AVERAGEX, MAXX
Medidas en iteradoresDisparan transición de contexto en cada filaSUMX(Ventas, [Medida])
FILTER/SUMX/AVERAGEX/MAXX/ADDCOLUMNSConstruyen y recorren tablas virtualesTabla intermedia con ADDCOLUMNS + MAXX
CallbackDataIDEvaluaciones repetidas Formula↔Storage EngineCálculos condicionales complejos dentro de iteradores

Este recorrido, con ejemplos progresivos (de lo simple a lo complejo), funciona bien como estructura de artículo: cada sección se apoya en la anterior, terminando en el tema de rendimiento, que suele ser el gancho para lectores más avanzados.

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuarlo!

Puntuación media 0 / 5. Recuento de votos: 0

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *