Filtro y Contexto
CALCULATE, ALL, FILTER, KEEPFILTERS, TREATAS y toda la maquinaria que hace DAX único — domina el contexto y dominas DAX
Antes de nada: el contexto de filtro 🧠
HAY DOS TIPOS DE CONTEXTO EN DAX:
1. CONTEXTO DE FILA (Row Context)
Existe cuando DAX está iterando fila a fila (dentro de SUMX, AVERAGEX, columnas calculadas...).
En contexto de fila, puedes acceder a 'Tabla'[Columna] de la fila actual.
Es como estar leyendo un libro línea a línea. En cada línea "sabes" qué hay escrito en esa línea concreta.
2. CONTEXTO DE FILTRO (Filter Context)
Existe siempre. Lo crean los visuals, slicers, relaciones y CALCULATE.
Define qué filas son "visibles" en el momento del cálculo.
Es como tener unas gafas de colores puestas que solo te dejan ver ciertos datos. CALCULATE puede cambiarte las gafas.
La transición de contexto (Context Transition)
Cuando usas CALCULATE (o cualquier función que internamente llama a CALCULATE) dentro de un contexto de fila, convierte el contexto de fila en un contexto de filtro. Esto es crucial y muchas veces confuso al principio.
// En una columna calculada (contexto de fila):
'Productos'[VentasProducto] =
CALCULATE(SUM('Ventas'[Importe]))
// CALCULATE convierte el contexto de fila (estoy en la fila del producto X)
// en un filtro: CALCULATE(SUM, 'Productos'[ID] = X)
// Resultado: las ventas de ESE producto concreto
La transición de contexto es la causa del 80% de los bugs confusos en DAX. Si una medida dentro de un iterador da resultados raros, probablemente es un problema de transición de contexto. Cuando dudes, añade CALCULATE() alrededor de tu medida dentro del iterador.
CALCULATE — La función más importante de DAX
Cómo funciona paso a paso:
// CALCULATE hace esto en orden:
// 1. Toma el contexto de filtro actual
// 2. Aplica las modificaciones de los argumentos de filtro
// 3. Evalúa la expresión en el NUEVO contexto modificado
// 4. Devuelve el resultado
Ejemplo básico — filtrar por un valor:
// Total de ventas SOLO de España (independiente de lo que filtre el visual)
Ventas España = CALCULATE(SUM('Ventas'[Importe]), 'Clientes'[País] = "España")
// Si el visual está filtrado por "Francia" → CALCULATE sobreescribe ese filtro
// y devuelve las ventas de España
Ejemplo — añadir un filtro:
// Ventas de productos de la categoría "Electrónica" (se añade al filtro existente)
Ventas Electrónica =
CALCULATE(
SUM('Ventas'[Importe]),
'Productos'[Categoría] = "Electrónica"
)
// Si el visual ya filtra por "España" → resultado = ventas de España + Electrónica
Ejemplo — múltiples filtros (AND implícito):
// Ventas de Electrónica en España en 2024
Ventas Electrónica España 2024 =
CALCULATE(
SUM('Ventas'[Importe]),
'Productos'[Categoría] = "Electrónica",
'Clientes'[País] = "España",
'Calendario'[Año] = 2024
)
// Los filtros de CALCULATE se combinan con AND entre ellos
Ejemplo — OR dentro de CALCULATE:
// Ventas de Electrónica O Informática
Ventas Electrónica o Informatica =
CALCULATE(
SUM('Ventas'[Importe]),
'Productos'[Categoría] IN {"Electrónica", "Informática"}
-- o equivalente:
-- FILTER(ALL('Productos'), 'Productos'[Categoría] = "Electrónica" || 'Productos'[Categoría] = "Informática")
)
Cuando el filtro de CALCULATE es una condición simple de igualdad (columna = valor), siempre usa la sintaxis directa:
CALCULATE([Medida], Tabla[Columna] = 'valor'). Es más eficiente que envolver todo en FILTER(ALL(Tabla), ...).
Ejemplo:
// Tabla de ventas solo de clientes premium
Ventas Premium =
CALCULATETABLE(
'Ventas',
'Clientes'[Segmento] = "Premium"
)
// Uso típico: contar filas de una tabla filtrada
Nº Pedidos Premium =
COUNTROWS(
CALCULATETABLE('Pedidos', 'Clientes'[Segmento] = "Premium")
)
ALL y familia — eliminar filtros selectivamente
La familia ALL es el complemento perfecto de CALCULATE. Si CALCULATE AÑADE filtros, ALL los QUITA. Juntos te dan control total sobre qué está filtrado y qué no.
Ejemplo 1 — % sobre el total general:
// ¿Qué porcentaje representan las ventas actuales sobre el total?
% sobre Total =
DIVIDE(
SUM('Ventas'[Importe]),
CALCULATE(SUM('Ventas'[Importe]), ALL('Ventas'))
)
// El denominador usa ALL para ignorar todos los filtros del visual
// → siempre devuelve el total absoluto, sin importar qué filtre el slicer
Ejemplo 2 — % sobre el total de categoría:
// % dentro de la categoría (sin perder el filtro de categoría)
% en Categoría =
DIVIDE(
SUM('Ventas'[Importe]),
CALCULATE(SUM('Ventas'[Importe]), ALL('Productos'[Producto]))
-- Quitamos el filtro de Producto pero mantenemos el de Categoría
)
Ejemplo 3 — ALL con tabla completa vs columna:
ALL('Ventas') -- quita TODOS los filtros de la tabla Ventas
ALL('Ventas'[Importe]) -- quita solo el filtro de la columna Importe
ALL('Productos'[Categoría], 'Productos'[Producto]) -- quita filtros de dos columnas
Ejemplo — Total por categoría (manteniendo el filtro de categoría):
// En una tabla con Categoría > Producto:
// Para calcular el % dentro de la categoría actual:
Total Categoría =
CALCULATE(
SUM('Ventas'[Importe]),
ALLEXCEPT('Productos', 'Productos'[Categoría])
-- Quita todos los filtros de Productos EXCEPTO Categoría
-- → da el total de la categoría actual
)
% en Categoría v2 = DIVIDE(SUM('Ventas'[Importe]), [Total Categoría])
ALLEXCEPT puede dar resultados inesperados cuando hay filtros cruzados desde otras tablas. En esos casos, la combinación ALL(...) + VALUES(...) suele ser más predecible. Es uno de los temas más avanzados de DAX — si los resultados no cuadran, revisa los artículos de SQLBI sobre ALLxxx.
Ejemplo — % sobre la selección visible (no sobre el total absoluto):
// El usuario filtra por "2024" con un slicer de año
// ALL daría el % sobre el total histórico
// ALLSELECTED da el % sobre el total de 2024 (lo que el usuario ve)
% sobre Selección =
DIVIDE(
SUM('Ventas'[Importe]),
CALCULATE(SUM('Ventas'[Importe]), ALLSELECTED('Productos'))
)
ALLSELECTED es probablemente la función más compleja de entender en DAX — incluso para expertos. Su comportamiento en modelos compuestos y composite models puede ser sorprendente. Si puedes lograr el resultado con ALL + SELECTEDVALUE, hazlo así.
Menciones breves:
- ALLNOBLANKROW — Como ALL pero excluye la fila en blanco generada por relaciones inválidas
- ALLCROSSFILTERED — Quita filtros incluyendo los que llegan por filtrado cruzado desde tablas relacionadas
REMOVEFILTERS
Ejemplo:
// Estas dos expresiones son equivalentes:
CALCULATE([Ventas], ALL('Productos'))
CALCULATE([Ventas], REMOVEFILTERS('Productos'))
// REMOVEFILTERS deja más claro que la intención es QUITAR filtros, no obtener una tabla
KEEPFILTERS
Ejemplo crucial — la diferencia:
// El visual tiene un slicer con "España" seleccionado
// SIN KEEPFILTERS: SOBREESCRIBE el filtro existente
Ventas Francia = CALCULATE([Ventas], 'Clientes'[País] = "Francia")
// → Resultado: ventas de FRANCIA, ignora que el slicer dice España
// CON KEEPFILTERS: INTERSECTA con el filtro existente
Ventas Francia KF = CALCULATE([Ventas], KEEPFILTERS('Clientes'[País] = "Francia"))
// → Resultado: BLANK (España ∩ Francia = vacío)
// Si el slicer dice "Francia" → devuelve ventas de Francia
// Si el slicer dice "España" → devuelve BLANK
KEEPFILTERS es útil cuando quieres que tu cálculo RESPETE la selección del usuario en lugar de ignorarla. Por ejemplo, en gráficos donde cada barra tiene su propio CALCULATE pero quieres que el slicer de año siga funcionando.
FILTER — filtrar tablas
Ejemplo básico:
// Tabla de solo clientes premium
ClientesPremium = FILTER('Clientes', 'Clientes'[Segmento] = "Premium")
// Dentro de CALCULATE:
Ventas Rentables =
CALCULATE(
SUM('Ventas'[Importe]),
FILTER('Ventas', 'Ventas'[Margen] > 0.15)
)
Ejemplo — FILTER con referencias a medidas (acceso a contexto externo):
// Productos cuyas ventas superan la media
Ventas Sobre Media =
CALCULATE(
COUNTROWS('Productos'),
FILTER(
'Productos',
CALCULATE(SUM('Ventas'[Importe])) > AVERAGE('Ventas'[Importe])
)
)
Usa FILTER solo cuando necesitas una condición compleja que no puedes expresar como argumento directo de CALCULATE. Para condiciones simples (columna = valor, columna IN lista), la sintaxis directa de CALCULATE es más eficiente que FILTER(ALL(tabla), condición).
VALUES, DISTINCT, IN — obtener valores únicos
Ejemplo:
// Los meses actualmente visibles en el visual
Meses Visibles = COUNTROWS(VALUES('Calendario'[Mes]))
// Iterar sobre los productos visibles
Mejor Producto =
MAXX(VALUES('Productos'[Nombre]), CALCULATE(SUM('Ventas'[Importe])))
Diferencia VALUES vs DISTINCT:
// VALUES incluye fila de BLANK si hay foreign keys sin match
// DISTINCT no la incluye
// En modelos bien diseñados, el resultado suele ser igual
// En modelos con datos sucios o relaciones problemáticas, puede diferir
Ejemplo:
// Filtrar varias categorías sin OR encadenados
Ventas Top Categorías =
CALCULATE(
SUM('Ventas'[Importe]),
'Productos'[Categoría] IN {"Electrónica", "Informática", "Audio"}
)
// Equivalente más verbose:
CALCULATE(
SUM('Ventas'[Importe]),
OR(OR('Productos'[Categoría] = "Electrónica",
'Productos'[Categoría] = "Informática"),
'Productos'[Categoría] = "Audio")
)
SELECTEDVALUE — leer slicers
Ejemplo básico — leer un slicer:
// Mostrar el país seleccionado en el título del visual
País Seleccionado = SELECTEDVALUE('Clientes'[País], "Todos los países")
// Un slicer con "España" → "España"
// Slicer con múltiples países seleccionados → "Todos los países"
// Sin slicer → "Todos los países"
Ejemplo — lógica condicional basada en slicer:
// Slicer de tipo de análisis: "Ventas" o "Margen"
Métrica Seleccionada =
SWITCH(
SELECTEDVALUE('Selector'[Tipo]),
"Ventas", SUM('Ventas'[Importe]),
"Margen", SUM('Ventas'[Margen]),
"Unidades", SUM('Ventas'[Cantidad]),
SUM('Ventas'[Importe]) -- valor por defecto
)
Ejemplo — HASONEVALUE (complemento de SELECTEDVALUE):
// HASONEVALUE devuelve TRUE/FALSE
Tiene Un País = HASONEVALUE('Clientes'[País])
// Útil para condicionar cálculos o mostrar mensajes
TREATAS — relaciones virtuales
Ejemplo — pasar un filtro de una tabla a otra sin relación:
// Quieres filtrar Ventas por los productos que aparecen en una lista externa
VAR ProductosList = {"P001", "P002", "P003"}
VAR TablaFiltro = DATATABLE("ID", STRING, {{"P001"}, {"P002"}, {"P003"}})
Ventas Lista =
CALCULATE(
SUM('Ventas'[Importe]),
TREATAS(TablaFiltro, 'Productos'[ID])
)
Ejemplo — usar los valores de un parámetro What-If como filtro:
// Caso más realista
Ventas Escenario =
CALCULATE(
SUM('Ventas'[Importe]),
TREATAS(
VALUES('EscenarioParam'[Región]),
'Clientes'[Región]
)
)
Los 5 patrones más usados con CALCULATE
Patrón 1 — Porcentaje sobre el total:
% Total = DIVIDE([Ventas], CALCULATE([Ventas], ALL('Tabla')))
Patrón 2 — Porcentaje sobre el padre en una jerarquía:
% del Padre = DIVIDE([Ventas], CALCULATE([Ventas], ALLEXCEPT('Productos','Productos'[Categoría])))
Patrón 3 — Valor del año anterior:
Ventas Año Anterior = CALCULATE([Ventas], SAMEPERIODLASTYEAR('Calendario'[Fecha]))
Patrón 4 — Medida dinámica por slicer:
Medida Dinámica =
SWITCH(SELECTEDVALUE('KPI'[Nombre]),
"Ventas", [Total Ventas],
"Margen", [Total Margen],
"Clientes", [Clientes Únicos],
[Total Ventas])
Patrón 5 — Filtro sobre tabla relacionada:
Ventas Clientes Premium =
CALCULATE([Ventas], FILTER(RELATEDTABLE('Clientes'), 'Clientes'[Segmento] = "Premium"))
¡Enhorabuena! CALCULATE es la cima de DAX y ya la tienes. Ahora que entiendes el contexto, las funciones de Inteligencia de Tiempo (como SAMEPERIODLASTYEAR o TOTALYTD) serán mucho más fáciles de entender.