Qué es una función de ventana
Una función de ventana calcula un valor para cada fila mirando otras filas, y no elimina ninguna. Con ella añades un total acumulado o una posición junto a cada fila de la tabla.
La papelería quiere ver, día a día, cuánto lleva vendido en el mes. Un GROUP BY daría un solo total. Aquí hace falta el total hasta cada día.
Al terminar esta lección
Nota
Puedo añadir un total acumulado o una posición junto a cada fila sin perder ninguna fila.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Función de ventana | calcula un valor por fila mirando otras filas | |
| OVER | la palabra que abre la ventana | |
| Total acumulado | la suma de todo hasta la fila actual | |
| Posición | el lugar de una fila al ordenar | RANK() |
| Empate | dos filas con el mismo valor al ordenar |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
OVER añade un cálculo sin quitar filas
GROUP BY junta filas: devuelve una por grupo. Una función de ventana no junta nada. Cada fila sigue en el resultado y recibe un valor más.
La palabra OVER abre la ventana. Sin nada dentro, la ventana es toda la tabla. Con ORDER BY dentro, la ventana crece fila a fila.
Ejemplo resuelto: el total acumulado
| day | amount | running_total |
|---|---|---|
| 2026-03-02 | 120 | 120 |
| 2026-03-03 | 95 | 215 |
| 2026-03-04 | 140 | 355 |
| 2026-03-05 | 120 | 475 |
| 2026-03-06 | 160 | 635 |
| 2026-03-07 | 90 | 725 |
| 2026-03-08 | 150 | 875 |
| 2026-03-09 | 140 | 1015 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pruébalo: un total acumulado
-- Cada día con lo que llevamos vendido hasta ese día.
select day, amount,
sum(amount) over (order by day) as running_total
from daily_takings
order by day;
-- Cambia sum(amount) por count(*) y pulsa Run.RANK pone cada fila en su lugar
RANK() OVER (ORDER BY amount DESC) da el 1 al día con más ventas. Si dos días empatan, RANK les da la misma posición y se salta la siguiente.
DENSE_RANK no se salta ninguna. ROW_NUMBER nunca repite; añade un segundo criterio en ORDER BY para que el desempate sea siempre el mismo.
Ejemplo resuelto: tres formas de numerar
| day | amount | RANK | DENSE_RANK | ROW_NUMBER |
|---|---|---|---|---|
| 2026-03-06 | 160 | 1 | 1 | 1 |
| 2026-03-08 | 150 | 2 | 2 | 2 |
| 2026-03-04 | 140 | 3 | 3 | 3 |
| 2026-03-09 | 140 | 3 | 3 | 4 |
| 2026-03-02 | 120 | 5 | 4 | 5 |
| 2026-03-05 | 120 | 5 | 4 | 6 |
| 2026-03-03 | 95 | 7 | 5 | 7 |
| 2026-03-07 | 90 | 8 | 6 | 8 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pruébalo: las posiciones
-- La posición de cada día, del mejor al peor.
select day, amount,
rank() over (order by amount desc) as position
from daily_takings
order by position, day;
-- Cambia desc por asc y pulsa Run.LAG mira la fila anterior
LAG(amount) trae el valor de la fila anterior, según el orden de la ventana. Restarlo al valor actual da el cambio respecto al día de antes.
La primera fila no tiene anterior. Allí LAG devuelve NULL, que significa «sin dato». LEAD hace lo mismo hacia delante.
Ejemplo resuelto: el cambio de cada día
| day | amount | change |
|---|---|---|
| 2026-03-02 | 120 | NULL |
| 2026-03-03 | 95 | -25 |
| 2026-03-04 | 140 | 45 |
| 2026-03-05 | 120 | -20 |
| 2026-03-06 | 160 | 40 |
| 2026-03-07 | 90 | -70 |
| 2026-03-08 | 150 | 60 |
| 2026-03-09 | 140 | -10 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Pruébalo: la diferencia con ayer
-- La diferencia con el día anterior.
select day, amount,
amount - lag(amount) over (order by day) as change
from daily_takings
order by day;
-- Cambia lag por lead y mira qué fila queda vacía.Un error frecuente
Advertencia
Mucha gente escribe una función de ventana dentro de WHERE. DuckDB responde con un Binder Error: WHERE clause cannot contain window functions.WHERE se ejecuta antes que las ventanas. Calcula la ventana en una subconsulta y filtra fuera.
Pruébalo: filtrar por el total acumulado
-- Primero la ventana, dentro; después el filtro, fuera.
select day, running_total
from (
select day, sum(amount) over (order by day) as running_total
from daily_takings
)
where running_total > 500
order by day;
-- Cambia 500 por 800 y pulsa Run.