Window Functions Nuevo
WINDOW, OFFSET, INDEX, RANK, ROWNUMBER y sus funciones auxiliares — el poder de SQL en DAX
Avanzado
¿Qué son las Window Functions? 🤔
Para quienes vienen de SQL 💡
Si vienes del mundo SQL, las Window Functions de DAX son exactamente lo mismo que las funciones de ventana de SQL (ROW_NUMBER, RANK, LAG, LEAD, SUM OVER PARTITION BY...). Si no vienes de SQL, no te preocupes — te lo explicamos desde cero con ejemplos de Power BI. 😊
Si vienes del mundo SQL, las Window Functions de DAX son exactamente lo mismo que las funciones de ventana de SQL (ROW_NUMBER, RANK, LAG, LEAD, SUM OVER PARTITION BY...). Si no vienes de SQL, no te preocupes — te lo explicamos desde cero con ejemplos de Power BI. 😊
Concepto clave: Las Window Functions operan sobre un conjunto ordenado de filas (una 'ventana') y pueden acceder a filas específicas de esa ventana. A diferencia de CALCULATE que modifica filtros, estas funciones trabajan con la posición de las filas.
¿Cuándo las necesitas?
- Calcular un ranking dentro de grupos (top ventas por región)
- Acceder al valor de la fila anterior/siguiente en el modelo (no solo en el visual)
- Implementar acumulados sin inteligencia de tiempo
- Añadir números de fila únicos a una tabla calculada
- Crear tablas calculadas con lógica de ventana compleja
⚠️ Nivel avanzado
Las Window Functions son de nivel avanzado. Requieren entender bien el contexto de filtro y cómo DAX ordena las tablas. Si acabas de empezar con DAX, te recomendamos que primero domines CALCULATE, ALL y las funciones de Filtro antes de entrar aquí. Pero si ya tienes esa base... ¡vamos allá! 🚀
Las Window Functions son de nivel avanzado. Requieren entender bien el contexto de filtro y cómo DAX ordena las tablas. Si acabas de empezar con DAX, te recomendamos que primero domines CALCULATE, ALL y las funciones de Filtro antes de entrar aquí. Pero si ya tienes esa base... ¡vamos allá! 🚀
🪟
WINDOW — La función base
WINDOW(<desde>, <tipo_desde>, <hasta>, <tipo_hasta>, <relación>, [<ordenar_por>], [<relleno>])
Avanzado
Devuelve un subconjunto de filas de una relación (tabla), definido por posiciones absolutas o relativas. Es la función más potente y más compleja de las Window Functions. Las otras (OFFSET, INDEX, RANK...) son casos especiales de WINDOW.
Parámetros explicados:
- desde/hasta: definen el inicio y fin de la ventana
- tipo: ABS (posición absoluta desde el inicio) o REL (posición relativa a la fila actual)
- relación: la tabla sobre la que opera (normalmente ALLSELECTED o VALUES)
- ordenar_por: cómo ordenar las filas (clave para que los cálculos sean correctos)
Ejemplo 1 — Total acumulado (implementado con WINDOW):
// Ventas acumuladas desde la primera fila hasta la actual
Ventas Acumuladas =
SUMX(
WINDOW(
1, ABS, -- desde la fila 1 (inicio absoluto)
0, REL, -- hasta la fila actual (0 relativo = actual)
ALLSELECTED('Calendario'[Mes]),
ORDERBY('Calendario'[Fecha], ASC) -- ordenado por fecha ascendente
),
[Total Ventas] -- suma las ventas de cada fila en la ventana
)
Ejemplo 2 — Media móvil de 3 períodos (implementado con WINDOW):
// Media de las 2 filas anteriores + la actual
Media Móvil 3M =
AVERAGEX(
WINDOW(
-2, REL, -- desde 2 filas antes
0, REL, -- hasta la fila actual
ALLSELECTED('Calendario'[Mes]),
ORDERBY('Calendario'[Fecha], ASC)
),
[Total Ventas]
)
💡 Cuándo usar WINDOW vs Visual Calculations
WINDOW es muy potente pero verbosa. Para casos simples como acumulados o medias móviles dentro de un visual, considera usar Visual Calculations (RUNNINGSUM, MOVINGAVERAGE) que son mucho más concisas. Usa WINDOW cuando necesites el cálculo en el modelo, no solo en un visual.
WINDOW es muy potente pero verbosa. Para casos simples como acumulados o medias móviles dentro de un visual, considera usar Visual Calculations (RUNNINGSUM, MOVINGAVERAGE) que son mucho más concisas. Usa WINDOW cuando necesites el cálculo en el modelo, no solo en un visual.
↕️
OFFSET — Acceso relativo a filas
OFFSET(<delta>, <relación>, [<ordenar_por>], [<relleno>], [<matchby>], [<partitionby>])
Avanzado
Devuelve UNA fila de la tabla, desplazada N posiciones respecto a la fila actual. Similar a la función LAG/LEAD de SQL. Muy útil para comparaciones período a período desde el modelo.
Parámetros clave:
- delta: número de filas a desplazar (+1 = siguiente, -1 = anterior, -2 = hace dos filas)
- relación: la tabla base (ALLSELECTED, VALUES, etc.)
- orderby: cómo ordenar para saber qué es "anterior" y "siguiente"
- partitionby: reiniciar el offset para cada grupo (como PARTITION BY en SQL)
Ejemplo 1 — Variación vs mes anterior (desde el modelo):
// En una medida normal (no Visual Calculation) — reutilizable en cualquier visual
Ventas Mes Anterior =
CALCULATE(
[Total Ventas],
OFFSET(
-1, -- 1 fila atrás
ALLSELECTED('Calendario'[Mes]), -- sobre todos los meses visibles
ORDERBY('Calendario'[Fecha], ASC) -- ordenado por fecha
)
)
Variación MoM = [Total Ventas] - [Ventas Mes Anterior]
Variación MoM % = DIVIDE([Variación MoM], [Ventas Mes Anterior], BLANK())
Ejemplo 2 — OFFSET con PARTITIONBY (variación dentro de cada región):
// Variación vs mes anterior, reiniciando por región
Ventas Mes Anterior Regional =
CALCULATE(
[Total Ventas],
OFFSET(
-1,
ALLSELECTED('Calendario'[Mes]),
ORDERBY('Calendario'[Fecha], ASC),
BLANK(), -- relleno si no hay fila anterior
MATCHBY('Calendario'[Mes]), -- cómo identificar la fila actual
PARTITIONBY('Clientes'[Región]) -- reiniciar para cada región
)
)
Comparativa OFFSET vs inteligencia de tiempo:
| FUNCIÓN | CUÁNDO USARLA |
|---|---|
OFFSET(-1, ..., ORDERBY(Fecha)) |
Cuando ordenas por cualquier dimensión, no solo fechas |
SAMEPERIODLASTYEAR |
Cuando comparas exactamente año anterior por fecha |
PREVIOUSMONTH |
Cuando comparas el mes anterior completo del calendario |
OFFSET con fechas |
Cuando necesitas flexibilidad en el período de comparación |
🔢
INDEX — Acceso absoluto a filas
INDEX(<posición>, <relación>, [<ordenar_por>], [<relleno>], [<matchby>], [<partitionby>])
Avanzado
Como OFFSET pero con posición ABSOLUTA en vez de relativa. INDEX(1) siempre devuelve la primera fila. INDEX(-1) devuelve la última. Ideal cuando necesitas comparar con un valor fijo de referencia.
Ejemplo 1 — Comparar cada mes con el primer mes del período:
// Ventas del primer mes visible (referencia fija)
Ventas Primer Período =
CALCULATE(
[Total Ventas],
INDEX(
1, -- posición 1 = primer elemento
ALLSELECTED('Calendario'[Mes]),
ORDERBY('Calendario'[Fecha], ASC)
)
)
// Índice base 100 (el primer mes = 100, el resto en proporción)
Índice Base 100 =
DIVIDE([Total Ventas], [Ventas Primer Período]) * 100
Ejemplo 2 — Comparar con el último valor conocido:
// Ventas del último mes con datos
Ventas Último Mes =
CALCULATE(
[Total Ventas],
INDEX(
-1, -- -1 = último elemento
ALLSELECTED('Calendario'[Mes]),
ORDERBY('Calendario'[Fecha], ASC)
)
)
🏅
RANK y ROWNUMBER — Numeración y rankings
RANK([ties], <relación>, [<ordenar_por>], [<partitionby>])
Avanzado
Calcula el ranking de la fila actual dentro de una tabla ordenada. A diferencia de RANKX (que es un iterador), RANK trabaja directamente con la posición en la tabla. Disponible desde 2023.
Diferencia clave con RANKX:
| FUNCIÓN | CÓMO FUNCIONA | CUÁNDO USARLA |
|---|---|---|
| RANKX | Iterador — evalúa una expresión para cada fila de una tabla | En medidas, para rankings dinámicos en cualquier visual |
| RANK | Window Function — trabaja con la posición en la tabla base | En tablas calculadas, para rankings más eficientes y SQL-like |
Ejemplo RANK:
// Ranking de productos por ventas (en tabla calculada)
Tabla Rankings =
ADDCOLUMNS(
SUMMARIZE('Ventas', 'Productos'[Nombre]),
"Total Ventas", [Total Ventas],
"Posición", RANK(
DENSE, -- sin saltos en empates
ALLSELECTED('Productos'[Nombre]),
ORDERBY([Total Ventas], DESC) -- el más vendido = posición 1
)
)
ROWNUMBER(<relación>, [<ordenar_por>], [<matchby>], [<partitionby>])
Avanzado
Asigna un número de fila único a cada fila. A diferencia de RANK, no hay empates — cada fila tiene un número distinto aunque los valores sean iguales.
Ejemplo ROWNUMBER:
// Añadir número de fila secuencial a una tabla
Productos Numerados =
ADDCOLUMNS(
'Productos',
"Nº", ROWNUMBER(
ALL('Productos'),
ORDERBY('Productos'[Nombre], ASC) -- ordenado alfabéticamente
)
)
// Resultado: Nº=1 para el primero, 2 para el segundo, etc.
// Usar ROWNUMBER como clave única en tablas calculadas sin ID natural
Clave Única =
ROWNUMBER(
ALL('MiTablaCalculada'),
ORDERBY('MiTablaCalculada'[Fecha], ASC, 'MiTablaCalculada'[ID], ASC)
)
⚙️
Funciones auxiliares de las Window Functions
Estas funciones no se usan solas — solo tienen sentido como argumentos dentro de WINDOW, OFFSET, INDEX, RANK o ROWNUMBER.
ORDERBY(<expresión>, [<orden>], <expresión2>, [<orden2>], ...)
Avanzado
Define el criterio de ordenación para las Window Functions. El orden es crucial — determina qué es "anterior" y "posterior". Puedes ordenar por múltiples columnas.
Ejemplos:
// Ordenar por fecha ascendente, desempatar por ID
ORDERBY('Calendario'[Fecha], ASC, 'Ventas'[ID], ASC)
// Ordenar por ventas descendente
ORDERBY([Total Ventas], DESC)
PARTITIONBY(<columna1>, <columna2>, ...)
Avanzado
Divide la tabla en grupos independientes (particiones). La Window Function reinicia para cada grupo. Equivalente al PARTITION BY de SQL.
Ejemplos:
// Acumulado que reinicia para cada región
PARTITIONBY('Clientes'[Región])
// Ranking independiente por país y categoría
PARTITIONBY('Clientes'[País], 'Productos'[Categoría])
MATCHBY(<columna1>, <columna2>, ...)
Avanzado
Define qué columnas identifican de forma única la fila actual. Necesario cuando la tabla tiene filas que podrían ser ambiguas en el contexto de la Window Function.
Ejemplo:
// Identificar la fila actual por mes y año (para OFFSET)
MATCHBY('Calendario'[Mes], 'Calendario'[Año])
🛠️
Patrones más usados con Window Functions
Patrón 1 — Acumulado genérico (por cualquier dimensión):
// Acumulado de ventas por número de cliente (no por fecha)
Ventas Acum por Cliente =
SUMX(
WINDOW(1, ABS, 0, REL,
ALLSELECTED('Clientes'[ID]),
ORDERBY('Clientes'[ID], ASC)
),
[Total Ventas]
)
Patrón 2 — Variación respecto al período anterior (reutilizable):
// Diferencia con el mes anterior — funciona en cualquier visual con meses
Variación Mes =
VAR VentasActual = [Total Ventas]
VAR VentasAnterior =
CALCULATE([Total Ventas],
OFFSET(-1, ALLSELECTED('Calendario'[Mes]),
ORDERBY('Calendario'[Fecha], ASC)))
RETURN DIVIDE(VentasActual - VentasAnterior, VentasAnterior, BLANK())
Patrón 3 — Ranking dentro de grupo:
// Ranking de vendedores dentro de su equipo
Ranking en Equipo =
RANK(DENSE,
ALLSELECTED('Vendedores'[Nombre]),
ORDERBY([Total Ventas], DESC),
PARTITIONBY('Vendedores'[Equipo])
)
🏆 Best Practice
Las Window Functions son potentísimas pero pueden ser lentas en modelos muy grandes. Úsalas cuando realmente las necesites (ranking persistente, acceso a filas vecinas desde el modelo). Para cálculos visuales simples, Visual Calculations suelen ser más eficientes.
Las Window Functions son potentísimas pero pueden ser lentas en modelos muy grandes. Úsalas cuando realmente las necesites (ranking persistente, acceso a filas vecinas desde el modelo). Para cálculos visuales simples, Visual Calculations suelen ser más eficientes.
🚀 Siguiente paso
¡Las Window Functions son de lo más potente que existe en DAX! Ahora que las entiendes, el siguiente paso son los Grupos de Cálculo — que te permitirán hacer que las medidas sean completamente dinámicas y reutilizables.
¡Las Window Functions son de lo más potente que existe en DAX! Ahora que las entiendes, el siguiente paso son los Grupos de Cálculo — que te permitirán hacer que las medidas sean completamente dinámicas y reutilizables.