LEAD() и LAG() — одни из самых полезных оконных инструментов в аналитике. Они позволяют заглянуть в соседнюю строку без JOIN самой таблицы к самой себе.

Если коротко:

  • LAG() берет значение из предыдущей строки
  • LEAD() берет значение из следующей строки

Именно поэтому их так любят в задачах на динамику, тренды и сравнение периодов.

Базовый пример

Представим таблицу дневной выручки:

SELECT
    date,
    revenue
FROM daily_sales

Теперь посмотрим на предыдущий день:

SELECT
    date,
    revenue,
    LAG(revenue) OVER (ORDER BY date) AS prev_revenue
FROM daily_sales

Что делает этот запрос:

  • сортирует строки по дате
  • для каждой строки берет значение revenue из предыдущей строки

Если нужна следующая строка:

SELECT
    date,
    revenue,
    LEAD(revenue) OVER (ORDER BY date) AS next_revenue
FROM daily_sales

Самый частый кейс: посчитать изменение

Например, день-к-дню:

SELECT
    date,
    revenue,
    LAG(revenue) OVER (ORDER BY date) AS prev_revenue,
    revenue - LAG(revenue) OVER (ORDER BY date) AS revenue_diff
FROM daily_sales

Так можно быстро увидеть:

  • рост
  • падение
  • аномалии

Если нужна относительная разница в процентах:

SELECT
    date,
    revenue,
    LAG(revenue) OVER (ORDER BY date) AS prev_revenue,
    100.0 * (revenue - LAG(revenue) OVER (ORDER BY date))
        / NULLIF(LAG(revenue) OVER (ORDER BY date), 0) AS revenue_pct_change
FROM daily_sales

Синтаксис

Упрощенно выглядит так:

LAG(column, offset, default_value) OVER (ORDER BY ...)
LEAD(column, offset, default_value) OVER (ORDER BY ...)

Где:

  • column — столбец, который берем
  • offset — на сколько строк сдвигаться
  • default_value — что вернуть, если строки нет

Пример со смещением

Взять не прошлый день, а значение 2 строки назад:

SELECT
    date,
    revenue,
    LAG(revenue, 2) OVER (ORDER BY date) AS revenue_2_days_ago
FROM daily_sales

Это удобно, если сравниваешь:

  • с позапрошлым периодом
  • с предыдущей неделей
  • с прошлым шагом в последовательности

Пример с default value

У первой строки нет предыдущего значения. Вместо NULL можно поставить что-то свое:

SELECT
    date,
    revenue,
    LAG(revenue, 1, 0) OVER (ORDER BY date) AS prev_revenue
FROM daily_sales

Теперь для первой строки вернется 0.

Но тут важно быть аккуратным: иногда 0 бизнес-смысла не имеет, и NULL честнее.

Очень полезный кейс: сравнение внутри группы

Например, по каждому пользователю отдельно:

SELECT
    user_id,
    event_time,
    amount,
    LAG(amount) OVER (
        PARTITION BY user_id
        ORDER BY event_time
    ) AS prev_amount
FROM payments

Вот здесь особенно важен PARTITION BY.

Без него сравнение шло бы между всеми строками подряд, а тебе обычно нужно сравнение внутри одного пользователя, товара, отдела или региона.

Где это реально используется

LEAD() и LAG() часто применяют для:

  • сравнения выручки с прошлым днем
  • анализа последовательности событий
  • поиска изменений статуса
  • расчета интервалов между событиями
  • сравнения текущей цены с предыдущей
  • анализа retention и повторных покупок

Например, посчитать интервал между двумя действиями:

SELECT
    user_id,
    event_time,
    LAG(event_time) OVER (
        PARTITION BY user_id
        ORDER BY event_time
    ) AS prev_event_time
FROM events

А затем можно посчитать разницу во времени.

LEAD и LAG против self join

То же самое иногда можно сделать через JOIN таблицы самой к себе. Но это:

  • длиннее
  • сложнее читать
  • чаще выглядит тяжелее и менее удобно

Поэтому если тебе нужно просто обратиться к соседней строке в упорядоченном наборе, LEAD() и LAG() почти всегда лучший вариант.

Частая ошибка №1: забыть ORDER BY

Без правильного ORDER BY соседняя строка не имеет смысла.

LAG(revenue) OVER (ORDER BY date)

Если порядок не задан или задан не по тому столбцу, результат будет формально корректным, но логически бесполезным.

Частая ошибка №2: забыть PARTITION BY

Если анализ идет по клиенту, товару или региону, чаще всего нужен PARTITION BY.

Иначе:

  • предыдущая строка может оказаться из другой группы
  • расчеты будут искажены

Частая ошибка №3: не обработать NULL

Для первой строки в группе у LAG() нет “прошлого”, а для последней строки у LEAD() нет “будущего”.

Поэтому часто нужно:

  • либо оставить NULL
  • либо задать default value
  • либо обернуть расчеты в COALESCE / NULLIF

Как быстро запомнить

Запоминалка очень простая:

  • LAG -> lag behind -> отстает -> смотрит назад
  • LEAD -> лидирует -> идет вперед -> смотрит вперед

Итог

LEAD() и LAG() — это удобный способ обратиться к соседней строке внутри упорядоченного набора данных.

  • LAG() берет прошлое значение
  • LEAD() берет следующее значение

Они особенно полезны для:

  • анализа динамики
  • сравнения периодов
  • поиска изменений
  • работы с временными рядами и событиями

Если ты часто сравниваешь “текущее” с “предыдущим” или “следующим”, эти функции точно должны быть в твоем рабочем наборе SQL-инструментов.