Aprende SQL · lección gratuita

Lección 30 · Funciones de ventana: OVER y PARTITION BY

Resumen

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.

Sintaxis / Conceptos

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

Ejemplos

-- 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 intentas SELECT ... WHERE SUM(salario) OVER (...) > 1000 obtendrás un error: las window functions no van en WHERE. Envuélvelas en una CTE y filtra fuera. Tampoco las combines como argumento de otro agregado normal en el mismo nivel.

Cheatsheet

Cláusula / FunciónQué 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 colReinicia 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 filtrarLas window functions no van en WHERE; filtra en un nivel externo

---

← Operaciones de conjuntos: UNION, INTERSECT, EXCEPTRanking: ROW_NUMBER, RANK, DENSE_RANK →

Ver todas las lecciones de Aprende SQL →