La idea
Casi todo el DAX que has escrito hasta ahora lee una columna entera: SUM(order_items[line_total]).
Un iterador es la otra forma de leer una tabla. SUMX, AVERAGEX, MAXX, RANKX y COUNTX
reciben una tabla y una expresión, visitan la tabla fila por fila, calculan la expresión en esa
fila y solo después reúnen los resultados (los suman, los promedian, los ordenan).
Mientras el iterador está parado en una fila, las columnas de esa fila están disponibles por su nombre. Esa "fila actual" es el contexto de fila. Es una posición en una tabla y nada más. No es un filtro, y esa diferencia está detrás de casi todos los errores con iteradores.
Figura 1
SUMX recorriendo el pedido 1004, línea por línea
| order_item_id | quantity | unit_price | discount_pct |
|---|---|---|---|
| 10 | 1 | 92.99 | 10 |
| 11 | 2 | 20.99 | 10 |
| 12 | 1 | 149.00 | 10 |
| 13 | 2 | 66.99 | 10 |
| order_item_id | net |
|---|---|
| 10 | 83.69 |
| 11 | 37.78 |
| 12 | 134.10 |
| 13 | 120.58 |
| Net Revenue |
|---|
| $376.15 |
- Cuatro líneas de un pedido real.
SUMX(order_items, quantity * unit_price * (1 - discount_pct / 100))las visitará una a una. - Línea 10: mientras el iterador está parado en esta fila,
quantity,unit_priceydiscount_pctsignifican los valores de esta fila. Eso es el contexto de fila. 1 x 92.99 x 0.9 = 83.69. - Las líneas 11, 12 y 13 siguen el mismo camino. Cada una se calcula por separado antes de sumar nada.
- Solo ahora suma la función X: 83.69 + 37.78 + 134.10 + 120.58 = 376.15.
SUM(quantity) * SUM(unit_price)daría 6 x 329.97 = 1,979.82: cada unidad por cada precio. Multiplicar antes de sumar es justo lo que hace un iterador.
Por qué existe
La Finance Controller de Harbor & Pine quiere el ingreso neto construido a partir de quantity,
unit_price y discount_pct. El número que necesita no existe como columna: es un producto, por
línea, que después hay que sumar. SUM solo puede sumar una columna que ya está ahí.
Quien viene de las hojas de cálculo agregaría una columna auxiliar, la rellenaría hacia abajo y la sumaría. Un iterador es esa columna auxiliar sin la columna: la multiplicación ocurre en memoria, justo para las filas que la celda puede ver, y no se guarda nada en el modelo. El archivo pesa menos y el número sigue vivo: cambia el segmentador y cambian las filas que se recorren.
Las funciones X son una familia con una sola forma: SUMX, AVERAGEX, MINX, MAXX y COUNTX
solo se diferencian en cómo reúnen los resultados por fila. Aprende una y las sabes todas.
Cómo funciona por dentro
SUMX(tabla, expresión) se ejecuta en dos etapas, y el orden importa.
- Decidir qué filas existen. El contexto de filtro de la celda (encabezado de fila,
segmentadores, filtros de
CALCULATE) reduce primerotabla. UnSUMXsobreorder_itemsen la fila de Lighting de la matriz recorre solo las líneas de Lighting. - Recorrerlas. Para cada fila que queda, se evalúa la expresión con esa fila como contexto de fila y luego se combinan los resultados.
Como la expresión se ejecuta una vez por fila, puede usar columnas de la fila
(order_items[quantity]) y, por una relación de muchos a uno, columnas de la fila padre
(RELATED(products[unit_cost])). En sentido contrario no llega sin ayuda: desde un producto no ves
automáticamente sus líneas.
Transición de contexto
Ese "sin ayuda" es el caso interesante. Dentro de una columna calculada o de un iterador, un
SUM(order_items[line_total]) normal ignora la fila en la que estás. El producto actual es una
posición, no un filtro, así que nada llega a order_items.
CALCULATE lo arregla. Cuando se ejecuta dentro de un contexto de fila, toma cada columna de la
fila actual, las convierte en filtros y evalúa su expresión bajo esos filtros. Eso es la
transición de contexto. Para el producto 135 se convierte en product_id = 135, que baja por la
relación hasta order_items como lo haría cualquier segmentador.
Figura 2
Una columna calculada en products: contexto de fila frente a un filtro
products
- product_id
- name
- category
product_id = 135
orders
- order_id
- channel
- status
order_items
- order_id
- product_id
- line_total
- Una columna calculada se evalúa una vez por cada fila de
products. Para el producto 135 el motor está parado en esa fila: un contexto de fila. SUM(order_items[line_total])le pide filas aorder_items. Nada las ha filtrado: un contexto de fila no es un filtro y no viaja por las relaciones.- Envuélvelo en
CALCULATE. La transición de contexto convierte la fila actual en un filtro,product_id = 135, y ese filtro baja como cualquier otro.
Con números se entiende mejor: las mismas dos fórmulas en una columna calculada de products.
Figura 3
La misma columna, con y sin CALCULATE
| product_id | name | bare_sum | with_calculate |
|---|---|---|---|
| 134 | Sable Linen Table Lamp | $6,516,344 | $51,423 |
| 135 | Ridge Ceramic Chandelier | $6,516,344 | $89,718 |
| 186 | Birch Brass Floor Mirror | $6,516,344 | $46,536 |
bare_sum = SUM(order_items[line_total]). Todos los productos muestran las ventas de toda la tienda.with_calculate = CALCULATE(SUM(order_items[line_total])). Ahora cada producto suma solo sus propias líneas.
Elegir la tabla que se recorre
La tabla que le pasas al iterador decide qué es un "elemento". AVERAGEX(order_items, ...) es un
promedio por línea de pedido. AVERAGEX(orders, [Sales]) es un promedio por pedido, que es lo que
significa "valor medio del pedido": se visita cada pedido, se suman sus propias líneas mediante la
transición de contexto y se promedian esos totales. La misma función con otra tabla responde otra
pregunta de negocio. Antes de escribir el primer argumento, pregúntate qué es una unidad de ese
promedio.
La misma idea sirve para un margen ponderado. Recorre order_items y, en cada línea, sube a su
producto con RELATED(products[unit_cost]). El costo se multiplica por la cantidad de esa línea,
así que los productos que más se venden pesan más. Promediar el margen de los 120 productos daría a
todos el mismo peso, y no es lo que vio la caja.
Las medidas llevan un CALCULATE invisible
Una referencia a una medida, [Sales], se comporta como si estuviera envuelta en CALCULATE. Por
eso SUMX(products, [Sales]) devuelve las ventas propias de cada producto sumadas, mientras que
SUMX(products, SUM(order_items[line_total])) repite el total de la tienda una vez por producto. La
primera convierte la fila en un filtro; la segunda no.
También significa que los iteradores tienen un precio. Una medida dentro de un SUMX sobre una
tabla grande dispara una transición por fila, lo cual es lento y, con cien mil filas, se nota.
Itera sobre la tabla pequeña (products, 120 filas) y no sobre la grande (líneas de pedido, más de
30,000) cuando la respuesta es la misma.
Dónde probarlo en Power BI Desktop
Usa Home → New measure para un iterador y Table tools → New column para una columna
calculada. Para ver qué hace un contexto de fila, escribe la misma expresión en ambos sitios y
compara. La DAX query view te deja ejecutar EVALUATE ADDCOLUMNS(products, "x", [Sales]) y leer
el resultado como una tabla.
Errores comunes
"SUM(quantity) * SUM(price) es lo mismo que sumar cantidad por precio"
Multiplicar dos totales multiplica cada cantidad por cada precio, no el par de cada línea. Para el pedido 1004 da 1,979.82 en vez de 376.15, y la diferencia crece con la tabla. Siempre que un número sea un producto o un cociente de dos columnas por fila, es un iterador.
"Un contexto de fila filtra las otras tablas"
No. Estar parado en la fila de un producto solo indica de qué fila leer las columnas, nada más. La
tabla order_items ve todas las líneas hasta que algo crea un filtro. Ese algo es CALCULATE o una
referencia a una medida.
"Los iteradores son siempre más lentos, así que hay que evitarlos"
Un iterador sobre una expresión de columnas (SUMX(order_items, quantity * unit_price)) es barato:
lo resuelve el motor de almacenamiento. El patrón lento es una medida evaluada una vez por cada fila
de una tabla grande. Elige la tabla más pequeña que dé la respuesta correcta y mantén la expresión
simple.
Ponte a prueba
Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.
No del todo. Estar en una fila solo te da las columnas de esa fila. No filtra otras tablas, así que A no puede saber en qué producto está parada.Prueba otra respuesta.
No del todo. Es al revés. El SUM sin más no tiene nada que filtre order_items, y CALCULATE es lo que crea el filtro.Prueba otra respuesta.
Correcto. A tiene contexto de fila pero ningún filtro, así que suma todas las líneas. El CALCULATE de B convierte la fila actual en product_id = 135, que fluye hasta order_items.
No del todo. Una columna calculada no ve ningún filtro hasta que CALCULATE (o la referencia a una medida) convierte la fila en uno. B hace exactamente eso.Prueba otra respuesta.
Lección hecha: ya está marcada en el mapa.