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-инструментов.