Menu

Funciones de ventana en SQLite: OVER y PARTITION BY

Cómo funcionan las window functions en SQLite: OVER, PARTITION BY, funciones de ranking, LAG/LEAD y frames para calcular totales acumulados.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Una función de ventana añade una columna sin colapsar las filas

GROUP BY reduce muchas filas a una sola. Las funciones de ventana en SQLite hacen algo distinto: calculan un valor sobre un conjunto de filas relacionadas, pero conservan todas las filas originales en el resultado. Así obtienes el detalle fila por fila y el agregado, uno al lado del otro.

La estructura es siempre la misma: una función, seguida de OVER (...).

La columna total_all muestra el gran total de todas las filas, repetido en cada línea. Las filas originales quedan intactas. Compáralo con SELECT SUM(amount) FROM sales: el número es el mismo, pero solo te devuelve una fila. Con las funciones de ventana en SQLite obtienes ambas vistas a la vez.

PARTITION BY: agregar dentro de grupos

Un OVER () vacío agrega sobre toda la tabla. Si añades PARTITION BY, agregas dentro de grupos, parecido a GROUP BY, pero sin colapsar las filas.

Cada fila trae el total de su región y la porción que le corresponde de ese total. Con un GROUP BY clásico perderías el detalle por empleado. Ese es justo el gran punto a favor de las funciones de ventana en SQLite: te dan el detalle y el agregado en una sola consulta.

Ranking en SQLite: ROW_NUMBER, RANK y DENSE_RANK

La familia de funciones de ranking numera las filas siguiendo el ORDER BY que pongas dentro del OVER. Las tres variantes se diferencian en cómo tratan los empates.

Al leer el resultado:

  • ROW_NUMBER() siempre devuelve valores únicos: los empates se rompen de forma arbitraria. Úsalo cuando necesites un número estable y distinto para cada fila.
  • RANK() asigna el mismo puesto a las filas empatadas, pero luego se salta los números siguientes. Si dos jugadores empatan en el puesto 1, el siguiente recibe el puesto 3.
  • DENSE_RANK() también respeta los empates, pero no se salta posiciones. El siguiente puesto sería el 2.

Para sacar los "top N por grupo", combina la función de ranking con PARTITION BY y filtra desde una consulta externa, ya que WHERE no puede referenciar funciones de ventana directamente:

Los dos que más facturan en cada región.

LAG y LEAD: mirar filas vecinas

LAG(col) devuelve el valor de col de la fila anterior dentro de la ventana. LEAD(col), en cambio, mira hacia adelante. Ambas funciones vienen de maravilla cuando quieres analizar cómo cambian los datos en el tiempo.

En la primera fila, ayer es NULL porque no hay nada antes. Puedes definir un valor por defecto: LAG(celsius, 1, celsius) OVER (ORDER BY day) usaría el valor de hoy cuando no exista una fila anterior.

LEAD hace justo lo contrario. Si combinas ambas con PARTITION BY, obtienes secuencias por grupo, como comparar las ventas de este mes con las del mes anterior dentro de cada región.

Total acumulado en SQLite con window functions

Si añades ORDER BY dentro de OVER, las funciones de agregación como SUM, AVG o COUNT empiezan a calcularse de forma acumulativa:

Dos detalles que conviene notar:

  • SUM(amount) OVER (ORDER BY day) es un total acumulado. Cuando escribes ORDER BY sin definir un marco explícito, el marco por defecto va "desde el inicio de la ventana hasta la fila actual".
  • La segunda columna sí define un marco explícito: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW. Esa es una ventana deslizante de 3 filas, o sea, una media móvil.

El modelo mental para los marcos: cada función de ventana se evalúa sobre un marco de filas definido respecto a la fila actual. Algunos marcos típicos:

  • ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: total acumulado (es el valor por defecto implícito).
  • ROWS BETWEEN N PRECEDING AND CURRENT ROW: ventana móvil hacia atrás.
  • ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING: toda la partición.

ROWS cuenta filas físicas. También existe RANGE, que agrupa por valor: viene muy bien cuando hay empates en la columna del ORDER BY y quieres tratarlos como un único paso.

FIRST_VALUE, LAST_VALUE y NTILE

Hay algunas funciones de ventana más que vale la pena conocer:

  • FIRST_VALUE y LAST_VALUE devuelven el primer o el último valor dentro del marco. Con LAST_VALUE hay que prestar atención al marco: por defecto termina en CURRENT ROW, así que normalmente vas a querer ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING para obtener de verdad el último valor de la partición.
  • NTILE(n) reparte las filas en n cubos de tamaño parecido — muy útil para cuartiles, percentiles o particiones tipo A/B.

Nombrar una ventana con WINDOW

Cuando varias columnas comparten la misma cláusula OVER (...), repetirla una y otra vez se vuelve pesado. SQLite te permite nombrar una ventana una sola vez y reutilizarla:

Misma consulta, mucho menos ruido. La cláusula WINDOW va después de WHERE/GROUP BY/HAVING y antes de ORDER BY.

Window functions vs GROUP BY en SQLite

Ambas opciones implican agregación, pero responden a preguntas distintas:

  • GROUP BY reduce. Una fila por grupo. Úsalo cuando solo te interese el resumen.
  • Las funciones de ventana conservan las filas. Cada fila de entrada sobrevive, con columnas calculadas extra al lado.

Si alguna vez te ves haciendo un GROUP BY y luego volviendo a unir los agregados con la tabla original, es una señal clarísima de que una función de ventana resolvería todo en una sola consulta.

Un par de tropiezos habituales

  • WHERE no puede referenciar funciones de ventana. Los filtros se aplican antes de calcular las ventanas. Envuelve la consulta en una subconsulta o CTE y filtra desde fuera.
  • Los marcos implícitos engañan. SUM(x) OVER (ORDER BY y) produce un total acumulado porque el marco por defecto es RANGE UNBOUNDED PRECEDING. Si lo que querías era la suma completa de la partición, escribe OVER (PARTITION BY ...) sin ORDER BY, o define el marco de forma explícita.
  • LAST_VALUE sorprende a todo el mundo la primera vez. Como el marco por defecto termina en la fila actual, devuelve el valor actual y no el último de la partición. Toca redefinir el marco.
  • Las funciones de ventana requieren SQLite 3.25+ (publicado en 2018). Cualquier instalación medianamente moderna las soporta, pero algunos entornos embebidos van con retraso.

Lo que viene: columnas generadas

Las funciones de ventana son cálculo en tiempo de consulta. La siguiente página trata el cálculo en tiempo de almacenamiento: las columnas generadas, donde el valor de la columna se define mediante una expresión y se actualiza automáticamente conforme cambian los datos subyacentes.

Preguntas frecuentes

¿Qué son las funciones de ventana en SQLite?

Las funciones de ventana (window functions) calculan un valor sobre un conjunto de filas relacionadas con la fila actual, sin colapsarlas como hace GROUP BY. Basta con añadir una cláusula OVER (...) a funciones como ROW_NUMBER(), RANK(), SUM() o LAG() para definir la ventana. Cada fila de entrada sigue apareciendo en el resultado: simplemente obtienes una columna calculada extra.

¿Cuál es la diferencia entre RANK y DENSE_RANK en SQLite?

Ambas asignan un ranking según el ORDER BY, pero gestionan los empates de forma distinta. RANK() deja huecos tras los empates: si dos filas empatan en el puesto 1, la siguiente recibe el 3. DENSE_RANK() no deja huecos, así que la siguiente fila pasa a ser el 2. Usa DENSE_RANK() cuando quieras puestos consecutivos y RANK() cuando esos saltos tengan sentido.

¿Cómo calculo un total acumulado en SQLite?

Se hace con SUM(columna) OVER (ORDER BY ...) y un frame de ventana. Por defecto, un ORDER BY dentro de OVER aplica el frame RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, que es justo lo que necesitas para un total acumulado. Si añades PARTITION BY, el acumulado se reinicia en cada grupo.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR