Aprende SQL · lección gratuita
Las funciones de ventana (window functions) realizan cálculos sobre un conjunto de filas relacionadas con la fila actual, sin colapsarlas en una sola fila como hace GROUP BY. Cada fila conserva su detalle y, al lado, obtiene un valor agregado (suma, promedio, conteo, ranking…) calculado sobre su "ventana". Son la herramienta clave para reportes analíticos: totales por grupo junto al detalle, porcentajes sobre el total, comparativas y rankings.
función() OVER (...): la cláusula OVER convierte cualquier agregado (SUM, AVG, COUNT, MIN, MAX) en una función de ventana. Sin OVER, sería un agregado normal.PARTITION BY col: divide las filas en grupos independientes; la función se reinicia en cada partición. Es el equivalente "por ventana" del GROUP BY, pero conservando todas las filas.OVER () vacío: la ventana es toda la tabla (o el resultado tras WHERE/GROUP BY). Útil para comparar cada fila contra el total global.SELECT salario, SUM(salario) OVER (...) devuelve una fila por empleado, con el total repetido. Con GROUP BY obtendrías una sola fila por grupo.WHERE ni en GROUP BY/HAVING. Para filtrar por su resultado, envuélvelas en una subconsulta o CTE.La forma general es:
función_ventana(argumentos) OVER (
[PARTITION BY columna(s)]
[ORDER BY columna(s)]
[marco_de_ventana]
)
Orden de ejecución. Las funciones de ventana se evalúan después de FROM, WHERE, GROUP BY y HAVING, pero antes de ORDER BY y LIMIT finales. Por eso no puedes referenciarlas en WHERE: cuando el WHERE corre, la ventana todavía no se ha calculado. La solución estándar es una CTE o subconjunta:
WITH r AS (SELECT ..., SUM(x) OVER (...) AS total FROM t)
SELECT * FROM r WHERE total > 1000;
GROUP BY vs PARTITION BY. Ambos agrupan, pero GROUP BY produce una fila por grupo (pierdes el detalle), mientras PARTITION BY produce el mismo número de filas que la entrada, con el agregado adosado. Si necesitas el total del departamento junto a cada empleado, PARTITION BY es la respuesta; si solo quieres el total, usa GROUP BY.
SELECT nombre, salario, RANK() OVER (ORDER BY salario DESC) AS ranking FROM empleados ORDER BY ranking LIMIT 8
-- Salario de cada empleado junto al total y % de su departamento.
-- SUM(...) OVER (PARTITION BY ...) NO colapsa filas: una por empleado.
SELECT nombre,
departamento_id,
salario,
SUM(salario) OVER (PARTITION BY departamento_id) AS total_depto,
ROUND(salario * 100.0
/ SUM(salario) OVER (PARTITION BY departamento_id), 1) AS pct_del_depto
FROM empleados
ORDER BY departamento_id, salario DESC;
-- OVER () vacío = ventana global. Compara cada salario contra el promedio
-- global y contra el promedio de su propio departamento.
SELECT nombre,
departamento_id,
salario,
ROUND(AVG(salario) OVER (), 0) AS prom_global,
ROUND(AVG(salario) OVER (PARTITION BY departamento_id), 0) AS prom_depto,
salario - CAST(ROUND(AVG(salario)
OVER (PARTITION BY departamento_id)) AS INTEGER) AS dif_vs_depto
FROM empleados
ORDER BY departamento_id, salario DESC;
-- COUNT como ventana: cuántos empleados hay en cada departamento,
-- mostrado en cada fila sin perder el detalle individual.
SELECT departamento_id,
nombre,
cargo,
COUNT(*) OVER (PARTITION BY departamento_id) AS empleados_en_depto
FROM empleados
ORDER BY departamento_id, nombre;
💡 Si intentasSELECT ... WHERE SUM(salario) OVER (...) > 1000obtendrás un error: las window functions no van enWHERE. Envuélvelas en una CTE y filtra fuera. Tampoco las combines como argumento de otro agregado normal en el mismo nivel.
| Cláusula / Función | Qué hace |
|---|---|
agg() OVER (...) | Convierte SUM/AVG/COUNT/MIN/MAX en función de ventana |
OVER () | Ventana = todas las filas del resultado (total global) |
PARTITION BY col | Reinicia el cálculo en cada grupo; conserva todas las filas |
SUM(x) OVER (PARTITION BY d) | Total de x por grupo d, repetido en cada fila |
x * 100.0 / SUM(x) OVER (...) | Porcentaje de cada fila sobre el total de su partición |
| CTE para filtrar | Las window functions no van en WHERE; filtra en un nivel externo |
---
← Operaciones de conjuntos: UNION, INTERSECT, EXCEPTRanking: ROW_NUMBER, RANK, DENSE_RANK →