Раздел 10 · Databases

Нарастающий итог и разница соседних строк

junior~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Отчёты любят два вопроса: «сколько набежало к этому дню» и «насколько изменилось по сравнению с прошлым разом». Первый называют нарастающим итогом, второй разницей соседних строк. Оба решаются оконными функциями в одну строку, и на собеседовании проверяют, знаешь ли ты, как это сделать, и понимаешь ли ловушку с одинаковыми значениями.

Как спрашивают

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

Уточнение для сильных кандидатов: «Что изменится, если у двух заказов одна и та же дата?»

Короткий ответ

Нарастающий итог это SUM(amount) OVER (ORDER BY created_at): окно с сортировкой суммирует от первой строки до текущей. Для отдельного итога по каждому клиенту добавляю PARTITION BY customer_id. Разница с предыдущей строкой это amount - LAG(amount) OVER (ORDER BY created_at). Ловушка: по умолчанию рамка окна RANGE, и строки с одинаковым значением сортировки получают одинаковый итог. Если нужен счёт строго построчно, пишу ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW или добавляю уникальную колонку в ORDER BY.

Разбор: копилка

Представь копилку, в которую ты каждый день кладёшь деньги и записываешь в тетрадь две вещи: сколько положил сегодня и сколько всего лежит в копилке. Вторая колонка и есть нарастающий итог: вчерашнее «всего» плюс сегодняшний взнос.

В SQL эту вторую колонку считает агрегат с окном. Обычный SUM с GROUP BY схлопывает строки в одну, а SUM ... OVER оставляет каждую строку на месте и дописывает к ней сумму по её окну, как мы видели в уроке про LAG, LEAD и SUM OVER.

На учебной базе блока:

SELECT id, created_at, amount,
       sum(amount) OVER (ORDER BY created_at) AS running
FROM orders
ORDER BY created_at;
id | created_at | amount | running
---+------------+--------+--------
 1 | 2026-01-05 |   5000 |    5000
 2 | 2026-01-20 |   3000 |    8000
 3 | 2026-02-02 |   7000 |   15000
 4 | 2026-02-15 |   3000 |   18000
 5 | 2026-03-01 |   1200 |   19200

ORDER BY внутри окна превращает сумму «по всей таблице» в сумму «от начала до этой строки». Без него sum(amount) OVER () вернул бы 19200 в каждой строке.

Итог по каждому клиенту

Если нужна отдельная копилка у каждого клиента, добавляем PARTITION BY. Счёт начинается заново в каждой группе:

SELECT id, customer_id, created_at, amount,
       sum(amount) OVER (PARTITION BY customer_id ORDER BY created_at) AS running
FROM orders
ORDER BY customer_id, created_at;
id | customer_id | created_at | amount | running
---+-------------+------------+--------+--------
 1 |           1 | 2026-01-05 |   5000 |    5000
 2 |           1 | 2026-01-20 |   3000 |    8000
 4 |           1 | 2026-02-15 |   3000 |   11000
 3 |           2 | 2026-02-02 |   7000 |    7000
 5 |           3 | 2026-03-01 |   1200 |    1200

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.