Aprende SQL · lección gratuita
Cuando una función de ventana lleva ORDER BY, se activa el concepto de marco (frame): el subconjunto de filas, relativo a la fila actual, sobre el que se calcula. Esto habilita totales acumulados (running totals), medias móviles y cualquier agregado "hasta aquí" o "en esta ventana deslizante". Controlar el frame con ROWS o RANGE es lo que separa un SUM OVER plano de un análisis temporal real.
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: marco para el total acumulado: suma desde la primera fila hasta la actual.ROWS BETWEEN n PRECEDING AND CURRENT ROW: ventana deslizante de n+1 filas → medias móviles y sumas recientes.ROWS cuenta filas físicas; RANGE agrupa por valor: ante empates en el ORDER BY, RANGE incluye todas las filas con el mismo valor, ROWS solo las que van antes físicamente.ORDER BY y no especificas marco, SQLite usa RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Esto puede sorprender con valores repetidos.ORDER BY no hay frame deslizante: la ventana es toda la partición (útil para totales globales, no para acumulados).agg(col) OVER (
[PARTITION BY ...]
ORDER BY ...
{ ROWS | RANGE } BETWEEN <inicio> AND <fin>
)
Límites disponibles para <inicio>/<fin>:
| Límite | Significado |
|---|---|
UNBOUNDED PRECEDING | desde el principio de la partición |
n PRECEDING | n filas/valores antes |
CURRENT ROW | la fila actual |
n FOLLOWING | n filas/valores después |
UNBOUNDED FOLLOWING | hasta el final de la partición |
ROWS vs RANGE — la diferencia que importa. ROWS razona por posición física: "las 3 filas anteriores". RANGE razona por valor del ORDER BY: "todas las filas cuyo valor sea ≤ el actual". Si hay empates, RANGE los mete todos de golpe (mismo resultado para todas las filas con ese valor), mientras ROWS los va sumando una a una. Para series temporales con una fila por período (sin empates) ambos coinciden, pero con duplicados el resultado difiere. Regla práctica: para totales acumulados y medias móviles usa ROWS, que es predecible.
Cuidado con el default. Como el frame por defecto es RANGE, un SUM(x) OVER (ORDER BY x) sobre una columna con repetidos no da un acumulado fila-a-fila, sino que "salta" en cada valor distinto. Si quieres acumulado real, escribe ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW explícitamente.
SELECT fecha, SUM(1) OVER (ORDER BY fecha ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS pedidos_acumulados FROM pedidos LIMIT 6
-- Total acumulado de revenue por fecha (running total).
-- ROWS UNBOUNDED PRECEDING ... CURRENT ROW = suma desde el inicio hasta hoy.
WITH rev_dia AS (
SELECT p.fecha,
ROUND(SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)), 2) AS revenue
FROM pedidos p
JOIN detalle_pedidos d ON d.pedido_id = p.id
GROUP BY p.fecha
)
SELECT fecha,
revenue,
ROUND(SUM(revenue) OVER (ORDER BY fecha
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), 2) AS acumulado
FROM rev_dia
ORDER BY fecha;
-- Media móvil de 3 meses (mes actual + 2 anteriores).
-- Ventana deslizante: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW.
WITH rev_mes AS (
SELECT strftime('%Y-%m', p.fecha) AS mes,
ROUND(SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)), 2) AS revenue
FROM pedidos p
JOIN detalle_pedidos d ON d.pedido_id = p.id
GROUP BY mes
)
SELECT mes,
revenue,
ROUND(AVG(revenue) OVER (ORDER BY mes
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS media_movil_3m
FROM rev_mes
ORDER BY mes;
-- ROWS vs RANGE con empates reales: la columna cantidad tiene muchos valores
-- repetidos. Para cada valor, RANGE cuenta TODAS las filas con esa cantidad
-- (mismo resultado en el grupo), ROWS cuenta fila a fila.
SELECT cantidad,
COUNT(*) OVER (ORDER BY cantidad
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS conteo_range,
COUNT(*) OVER (ORDER BY cantidad
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS conteo_rows
FROM detalle_pedidos
ORDER BY cantidad
LIMIT 12;
💡 Si tu "total acumulado" se ve como escalones planos en vez de crecer fila a fila, casi seguro estás usando el frameRANGEpor defecto sobre una columna con valores repetidos. Cambia aROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
| Frame | Para qué |
|---|---|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW | Total acumulado (running total) |
ROWS BETWEEN n PRECEDING AND CURRENT ROW | Media móvil / suma de n+1 filas |
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING | Acumulado hacia adelante |
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW | Acumula por valor; agrupa empates (default con ORDER BY) |
ROWS | Cuenta filas físicas (predecible) |
RANGE | Agrupa por valor del ORDER BY (incluye todos los empates) |
---
← Acceso entre filas: LAG y LEADDistribución: NTILE y percentiles →