🎯

Filtro y Contexto

CALCULATE, ALL, FILTER, KEEPFILTERS, TREATAS y toda la maquinaria que hace DAX único — domina el contexto y dominas DAX

Intermedio Avanzado
🔍

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.

Analogía kawaii 🌸
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.

Analogía kawaii 🌸
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 fuente del 80% de los bugs en DAX
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

CALCULATE(<expresión>, <filtro1>, <filtro2>, ...)
Intermedio → Avanzado
Evalúa una expresión en un contexto de filtro MODIFICADO. Es la función más importante y más poderosa de DAX. Puede añadir, quitar o modificar filtros del contexto antes de evaluar la expresión.

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")
)
🏆 Best Practice
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), ...).
CALCULATETABLE(<tabla>, <filtro1>, <filtro2>, ...)
Avanzado
Igual que CALCULATE pero devuelve una TABLA en lugar de un escalar. Se usa para crear tablas filtradas que luego se pasan a otras funciones.

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.

ALL(<tabla_o_columna>)
Intermedio
Elimina todos los filtros de la tabla o columna indicada. Como modificador de CALCULATE, también puede eliminar filtros de todo el contexto. Fundamental para calcular porcentajes sobre totales.

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
ALLEXCEPT(<tabla>, <columna1>, <columna2>, ...)
Intermedio — Avanzado
Quita todos los filtros de la tabla EXCEPTO los de las columnas especificadas. Muy útil cuando quieres el total de grupo manteniendo un nivel de jerarquía.

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])
⚠️ Cuidado con filtros cruzados
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.
ALLSELECTED(<tabla_o_columna>)
Avanzado
Quita los filtros directos del contexto pero MANTIENE los filtros de los slicers y filtros de página (el "shadow filter context"). Perfecta para calcular % respecto a la selección actual del usuario.

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'))
)
⚠️ La función más compleja de DAX
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

REMOVEFILTERS(<tabla_o_columna>)
Intermedio
Alias más legible de ALL cuando se usa como modificador de CALCULATE. No devuelve tabla — solo se usa dentro de CALCULATE para quitar filtros. Mejora la legibilidad del código.

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

KEEPFILTERS(<filtro>)
Avanzado
Modificador de CALCULATE que cambia cómo se combina el nuevo filtro con el existente. Sin KEEPFILTERS, CALCULATE REEMPLAZA el filtro existente. Con KEEPFILTERS, lo INTERSECTA (AND con el filtro existente).

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
Cuándo usar KEEPFILTERS 💡
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

FILTER(<tabla>, <condición>)
Intermedio
Devuelve una tabla filtrada según una condición. Se usa principalmente dentro de otras funciones (CALCULATE, COUNTROWS, SUMX...) para crear subconjuntos de datos dinámicamente.

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])
    )
)
🏆 Best Practice
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

VALUES(<tabla_o_columna>)
Intermedio
Devuelve los valores únicos de una columna (o tabla) en el contexto de filtro actual. Incluye una fila en blanco si hay relaciones inválidas. Muy usada para iterar sobre valores únicos visibles.

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])))
DISTINCT(<columna>)
Intermedio
Muy similar a VALUES pero NO incluye la fila en blanco por relaciones inválidas. Útil cuando trabajas con columnas que pueden tener valores ambiguos y quieres solo los valores reales de los datos.

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
<valor> IN <tabla_de_una_columna> o <lista>
Intermedio
Operador (no función) que comprueba si un valor está en una lista o tabla. Muy útil para filtros de múltiples valores.

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

SELECTEDVALUE(<columna>, [<alternativa>])
Intermedio
Si hay exactamente UN valor seleccionado en el contexto para esa columna, lo devuelve. Si hay varios o ninguno, devuelve el valor alternativo (por defecto BLANK). Imprescindible para leer qué tiene seleccionado el usuario en un slicer.

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

TREATAS(<tabla>, <columna1>, <columna2>, ...)
Avanzado
Aplica los valores de una tabla como filtros de otras columnas, creando una "relación virtual" para el contexto de ese cálculo. Muy útil para hacer filtrajes cruzados entre tablas sin crear relaciones físicas en el modelo.

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"))
🚀 Siguiente paso
¡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.