LAG

LAG permite acceder al valor de una fila anterior dentro de la misma partición, sin necesidad de hacer un JOIN de la tabla consigo misma. Es la función de ventana por excelencia para análisis temporales: calcular variaciones mes a mes, detectar tendencias de crecimiento o caída, y comparar cada registro con su predecesor.

Antes de que existieran las funciones de ventana, obtener el valor de la fila anterior requería un LEFT JOIN complejo con subconsultas correlacionadas. LAG resuelve esto en una sola línea, haciendo que el código sea más legible y el rendimiento significativamente mejor.

Sintaxis

LAG(expresion, offset, valor_defecto) OVER (
    [PARTITION BY columna1, columna2, ...]
    ORDER BY columna1 [ASC|DESC], columna2 [ASC|DESC], ...
)

La función acepta tres argumentos. El primero, expresion, es la columna o expresión cuyo valor anterior quieres obtener. El segundo, offset, es opcional y define cuántas filas hacia atrás mirar (por defecto es 1). El tercero, valor_defecto, también opcional, es el valor que se devuelve cuando no existe fila anterior (por defecto es NULL). El ORDER BY es obligatorio porque sin un orden definido, "anterior" no tiene significado.

Comportamiento básico

En su forma más simple, LAG devuelve el valor de la columna indicada en la fila inmediatamente anterior:

SELECT
    mes,
    ingresos,
    LAG(ingresos) OVER (ORDER BY mes) AS ingresos_mes_anterior
FROM resumen_mensual
WHERE anio = 2024
ORDER BY mes;
mesingresosingresos_mes_anterior
157000.00NULL
251750.0057000.00
365600.0051750.00
472250.0065600.00
567660.0072250.00
679200.0067660.00

Enero no tiene mes anterior, así que LAG devuelve NULL. Febrero muestra 57000, que son los ingresos de enero. Marzo muestra 51750, los de febrero. La ventana se desliza fila a fila, siempre mirando una posición hacia atrás.

Usar un valor por defecto

Para evitar el NULL en la primera fila, puedes especificar un valor por defecto como tercer argumento:

SELECT
    mes,
    ingresos,
    LAG(ingresos, 1, 0) OVER (ORDER BY mes) AS ingresos_anterior
FROM resumen_mensual
WHERE anio = 2024
ORDER BY mes
LIMIT 3;
mesingresosingresos_anterior
157000.000.00
251750.0057000.00
365600.0051750.00

Ahora enero muestra 0 en lugar de NULL, lo cual es útil si necesitas hacer cálculos con ese valor sin preocuparte por los NULL.

Caso práctico: variación mes a mes

El uso más habitual de LAG es calcular el cambio porcentual entre periodos consecutivos. Este análisis es la base de cualquier informe financiero:

SELECT
    mes,
    ingresos,
    LAG(ingresos) OVER (ORDER BY mes) AS mes_anterior,
    ROUND(
        (ingresos - LAG(ingresos) OVER (ORDER BY mes))
        / LAG(ingresos) OVER (ORDER BY mes) * 100,
        2
    ) AS variacion_pct
FROM resumen_mensual
WHERE anio = 2024
ORDER BY mes;
mesingresosmes_anteriorvariacion_pct
157000.00NULLNULL
251750.0057000.00-9.21
365600.0051750.0026.76
472250.0065600.0010.14
567660.0072250.00-6.35
679200.0067660.0017.06

Febrero cayó un 9.21% respecto a enero. Marzo se recuperó con un crecimiento del 26.76%. Mayo volvió a caer un 6.35% para luego subir un 17% en junio. Este tipo de análisis sería mucho más complejo sin LAG, requiriendo un JOIN de la tabla consigo misma igualando cada mes con su predecesor.

Caso práctico: detectar tendencias de crecimiento

Combinando LAG con CASE puedes clasificar automáticamente cada periodo como crecimiento, caída o estable:

SELECT
    departamento,
    mes,
    total_ventas,
    LAG(total_ventas) OVER (
        PARTITION BY departamento
        ORDER BY mes
    ) AS ventas_anterior,
    CASE
        WHEN LAG(total_ventas) OVER (PARTITION BY departamento ORDER BY mes) IS NULL
            THEN 'Sin referencia'
        WHEN total_ventas > LAG(total_ventas) OVER (PARTITION BY departamento ORDER BY mes)
            THEN 'Crecimiento'
        WHEN total_ventas < LAG(total_ventas) OVER (PARTITION BY departamento ORDER BY mes)
            THEN 'Caída'
        ELSE 'Estable'
    END AS tendencia
FROM ventas_mensuales
WHERE anio = 2024
ORDER BY departamento, mes;
departamentomestotal_ventasventas_anteriortendencia
Marketing142000.00NULLSin referencia
Marketing238500.0042000.00Caída
Marketing345200.0038500.00Crecimiento
Marketing448900.0045200.00Crecimiento
Ventas185000.00NULLSin referencia
Ventas291200.0085000.00Crecimiento
Ventas391200.0091200.00Estable
Ventas498400.0091200.00Crecimiento

El PARTITION BY departamento hace que LAG se reinicie para cada departamento. Las ventas de enero en Marketing no se comparan con las de Ventas del mes anterior, sino que cada departamento tiene su propia secuencia independiente.

Caso práctico: offset mayor que 1

El segundo argumento de LAG permite mirar más de una fila hacia atrás. Esto es útil para comparaciones interanuales donde quieres comparar con el mismo mes del año anterior:

SELECT
    anio,
    trimestre,
    ingresos,
    LAG(ingresos, 4) OVER (ORDER BY anio, trimestre) AS mismo_trim_anio_anterior,
    ROUND(
        (ingresos - LAG(ingresos, 4) OVER (ORDER BY anio, trimestre))
        / LAG(ingresos, 4) OVER (ORDER BY anio, trimestre) * 100,
        2
    ) AS crecimiento_interanual
FROM resumen_trimestral
ORDER BY anio, trimestre;
aniotrimestreingresosmismo_trim_anio_anteriorcrecimiento_interanual
20231145000.00NULLNULL
20232162000.00NULLNULL
20233158000.00NULLNULL
20234198000.00NULLNULL
20241168000.00145000.0015.86
20242185000.00162000.0014.20
20243179000.00158000.0013.29
20244224000.00198000.0013.13

Con LAG(ingresos, 4), cada trimestre de 2024 se compara con el mismo trimestre de 2023 (4 trimestres antes). Los primeros 4 registros no tienen referencia y devuelven NULL. Este patrón es esencial en informes financieros donde la estacionalidad hace que la comparación mes a mes sea engañosa.

Manejo de NULL

Cuando la columna de la expresión contiene valores NULL, LAG los devuelve tal cual. No los salta ni busca el siguiente valor no nulo:

SELECT
    mes,
    ingresos,
    LAG(ingresos) OVER (ORDER BY mes) AS anterior
FROM resumen_mensual;
mesingresosanterior
157000.00NULL
2NULL57000.00
365600.00NULL

Si febrero tiene ingresos NULL, marzo obtendrá NULL como valor anterior, porque LAG devuelve exactamente lo que hay en la fila anterior, sin importar si es NULL o no. Para manejar estos casos, envuelve LAG en COALESCE:

SELECT
    mes,
    ingresos,
    COALESCE(
        LAG(ingresos) OVER (ORDER BY mes),
        0
    ) AS anterior_seguro
FROM resumen_mensual;

Combinación con otras funciones

LAG se combina naturalmente con funciones matemáticas y de formato para crear dashboards analíticos completos:

SELECT
    mes,
    ingresos,
    LAG(ingresos) OVER (ORDER BY mes) AS anterior,
    ingresos - LAG(ingresos) OVER (ORDER BY mes) AS diferencia_absoluta,
    CONCAT(
        CASE
            WHEN ingresos >= LAG(ingresos) OVER (ORDER BY mes) THEN '+'
            ELSE ''
        END,
        ROUND(
            (ingresos - LAG(ingresos) OVER (ORDER BY mes))
            / LAG(ingresos) OVER (ORDER BY mes) * 100,
            1
        ),
        '%'
    ) AS variacion
FROM resumen_mensual
WHERE anio = 2024
ORDER BY mes;
mesingresosanteriordiferencia_absolutavariacion
157000.00NULLNULLNULL
251750.0057000.00-5250.00-9.2%
365600.0051750.0013850.00+26.8%
472250.0065600.006650.00+10.1%
567660.0072250.00-4590.00-6.4%
679200.0067660.0011540.00+17.1%

El CONCAT con el CASE añade el signo + a los crecimientos positivos para mejorar la legibilidad. La diferencia absoluta muestra el monto exacto del cambio, y la variación lo expresa en porcentaje.

Practica con LAG

Usa el editor para acceder a filas anteriores:

Simulador SQL
Ctrl+Enter para ejecutar

En el siguiente artículo veremos LEAD para acceder a filas siguientes.

Escrito por Eduardo Lázaro