SQL window functions: clasificación, totales acumulados y analítica
Published: 2026-03-24
Las window functions realizan cálculos sobre un conjunto de filas relacionadas con la fila actual, sin colapsar el resultado en una sola fila de salida como hace GROUP BY. Son una de las características más potentes del SQL moderno, compatibles con SQLite (3.25+), PostgreSQL, MySQL (8.0+), SQL Server y Oracle.
Datos de ejemplo
Usamos dos tablas en todos los ejemplos:
Tabla employees:
| id | name | department | hire_date | salary |
|---|---|---|---|---|
| 1 | Alice | Engineering | 2023-01-15 | 95000 |
| 2 | Bob | Engineering | 2023-06-01 | 85000 |
| 3 | Carol | Engineering | 2024-03-10 | 85000 |
| 4 | Dave | Sales | 2023-02-20 | 72000 |
| 5 | Eve | Sales | 2023-09-05 | 68000 |
| 6 | Frank | Sales | 2024-01-15 | 65000 |
| 7 | Grace | Marketing | 2023-04-01 | 82000 |
| 8 | Henry | Marketing | 2024-06-01 | 70000 |
Tabla monthly_sales:
| id | employee_id | month | revenue |
|---|---|---|---|
| 1 | 1 | 2026-01 | 22000 |
| 2 | 1 | 2026-02 | 25000 |
| 3 | 1 | 2026-03 | 28000 |
| 4 | 2 | 2026-01 | 19000 |
| 5 | 2 | 2026-02 | 21000 |
| 6 | 2 | 2026-03 | 20000 |
| 7 | 3 | 2026-01 | 15000 |
| 8 | 3 | 2026-02 | 17000 |
| 9 | 3 | 2026-03 | 19000 |
| 10 | 4 | 2026-01 | 18000 |
| 11 | 4 | 2026-02 | 22000 |
| 12 | 4 | 2026-03 | 17000 |
| 13 | 5 | 2026-01 | 14000 |
| 14 | 5 | 2026-02 | 16000 |
| 15 | 5 | 2026-03 | 19000 |
| 16 | 6 | 2026-01 | 11000 |
| 17 | 6 | 2026-02 | 13000 |
| 18 | 6 | 2026-03 | 15000 |
| 19 | 7 | 2026-01 | 9000 |
| 20 | 7 | 2026-02 | 12000 |
| 21 | 7 | 2026-03 | 14000 |
| 22 | 8 | 2026-01 | 7000 |
| 23 | 8 | 2026-02 | 9000 |
| 24 | 8 | 2026-03 | 11000 |
Sintaxis de las window functions
Cada función de ventana usa la cláusula OVER para definir sobre qué filas operar:
function_name(...) OVER (
[PARTITION BY column]
[ORDER BY column]
[frame_clause]
)
- PARTITION BY: divide las filas en grupos (como GROUP BY, pero mantiene todas las filas)
- ORDER BY: define el orden de las filas dentro de cada partición
- Frame clause: limita la ventana a un rango de filas relativo a la fila actual
ROW_NUMBER()
ROW_NUMBER() asigna un número secuencial único a cada fila dentro de una partición. Los números siempre empiezan en 1 sin huecos ni empates.
Ejemplo: Numerar a todos los empleados por salario
Ejemplo: Numerar empleados dentro de cada departamento
Usando PARTITION BY, la numeración se reinicia en 1 para cada departamento:
RANK() y DENSE_RANK()
RANK() y DENSE_RANK() manejan los empates de forma diferente a ROW_NUMBER():
- ROW_NUMBER(): siempre único, desempate arbitrario
- RANK(): el mismo valor recibe el mismo rango y luego se saltan números (1, 2, 2, 4)
- DENSE_RANK(): el mismo valor recibe el mismo rango, sin huecos (1, 2, 2, 3)
Ejemplo: Comparar las tres funciones de clasificación
Bob y Carol ganan 85000; observa cómo cada función maneja el empate:
Ejemplo: Clasificar empleados dentro del departamento
NTILE()
NTILE(n) distribuye las filas en n grupos (buckets) aproximadamente iguales, numerados del 1 al n.
Ejemplo: Dividir empleados en cuartiles de salario
Ejemplo: Dividir empleados en dos niveles por departamento
LAG() y LEAD()
LAG(column, offset) accede a un valor de una fila anterior. LEAD(column, offset) accede a un valor de una fila posterior. Ambos tienen por defecto un desplazamiento de 1 y devuelven NULL cuando no hay fila.
Ejemplo: Comparación de ingresos mes a mes
Compara los ingresos de cada mes con el mes anterior para cada empleado:
Ejemplo: Ver el ingreso del mes siguiente
Window functions de agregación
Las funciones de agregación estándar (SUM, AVG, COUNT, MIN, MAX) pueden usarse como window functions con OVER. A diferencia de GROUP BY, cada fila se conserva en la salida.
Ejemplo: Salario de cada empleado frente al promedio del departamento
Ejemplo: Porcentaje del salario total
Calcula qué fracción de la nómina total representa cada empleado:
Ejemplo: Total acumulado y conteo acumulado
Totales acumulados por partición
Combina PARTITION BY y ORDER BY para calcular totales acumulados dentro de grupos.
Ejemplo: Ingresos acumulados por empleado
Ejemplo: Clasificación de ingresos mensuales entre todos los empleados
Encuentra cómo se clasificó cada empleado cada mes:
Marcos de ventana (window frames)
Un marco de ventana define exactamente qué filas relativas a la fila actual se incluyen en el cálculo. La cláusula de marco viene después de ORDER BY dentro de OVER.
Sintaxis
ROWS BETWEEN <start> AND <end>
Límites comunes:
UNBOUNDED PRECEDING: desde la primera fila de la particiónN PRECEDING: N filas antes de la fila actualCURRENT ROW: la fila actualN FOLLOWING: N filas después de la fila actualUNBOUNDED FOLLOWING: hasta la última fila de la partición
Ejemplo: Promedio móvil de 2 meses
Ejemplo: Mínimo y máximo de ingresos acumulados
FIRST_VALUE y LAST_VALUE
FIRST_VALUE(column) devuelve el primer valor en el marco de ventana. LAST_VALUE(column) devuelve el último valor. Cuidado: LAST_VALUE con el marco por defecto (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) solo ve hasta la fila actual.
Ejemplo: Comparar cada mes con el primer mes
Ejemplo: Comparar cada mes con el mejor mes
Usa ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING para ver la partición completa:
Ejemplo práctico: máximo perceptor por departamento
Un patrón común: usar una función de ventana en una subconsulta y luego filtrar por rango.
Ejemplo: Empleado mejor pagado en cada departamento
Ejemplo: Mes con mayores ingresos por empleado
Window functions vs. GROUP BY
Una diferencia clave: GROUP BY colapsa filas en filas de resumen, mientras que las window functions mantienen cada fila y añaden una columna calculada.
Ejemplo: Comparación lado a lado
GROUP BY devuelve una fila por departamento:
Las window functions mantienen todas las filas:
Casos de uso comunes de las window functions
- Clasificaciones y rankings: ROW_NUMBER, RANK, DENSE_RANK para consultas top-N
- Totales acumulados: SUM acumulativa para seguimiento financiero e inventarios
- Comparación periodo a periodo: LAG/LEAD para análisis mes a mes o año contra año
- Promedios móviles: suavizar datos con AVG basado en marcos
- Análisis de percentiles: NTILE para dividir datos en grupos iguales
- Eliminación de duplicados: ROW_NUMBER para elegir una fila por grupo
- Análisis de huecos: LAG para detectar secuencias faltantes o cambios inusuales
Consejos de rendimiento
- Indexa las columnas de ORDER BY: las window functions ordenan datos internamente; los índices ayudan
- Minimiza las particiones: un gran número de particiones incrementa el cómputo
- Reutiliza definiciones de ventana: muchas bases de datos admiten cláusulas
WINDOWpara evitar repetir la misma especificaciónOVER - Filtra después de aplicar ventanas: las window functions se ejecutan antes de
WHERE, así que usa subconsultas o CTE para filtrar por rango - Evita marcos innecesarios: especifica cláusulas de marco solo cuando necesites un comportamiento no predeterminado
Resumen
- Las window functions calculan valores sobre filas relacionadas sin colapsar el conjunto de resultados
- ROW_NUMBER, RANK, DENSE_RANK asignan posiciones ordinales a las filas
- LAG y LEAD acceden a valores de filas anteriores o posteriores
- NTILE distribuye filas en grupos de tamaño similar
- FIRST_VALUE y LAST_VALUE devuelven valores de los extremos de la ventana
- Las funciones de agregación (SUM, AVG, COUNT, MIN, MAX) con
OVERmantienen todas las filas mientras calculan métricas a nivel de grupo - PARTITION BY crea grupos independientes; ORDER BY define la secuencia de filas
- Los marcos de ventana (
ROWS BETWEEN ... AND ...) afinan qué filas se incluyen - Usa subconsultas para filtrar por resultados de window functions (por ejemplo, top-N por grupo)
- Compatibles con SQLite 3.25+, PostgreSQL, MySQL 8.0+, SQL Server y Oracle
Prueba a modificar los ejemplos para experimentar con diferentes particiones, ordenaciones y cláusulas de marco y ver cómo las window functions pueden simplificar tus consultas analíticas.