Платёжный календарь в SQL: сколько денег должно поступить по неделям
Финансовому менеджеру нужно понять, сколько денег ожидается на следующей неделе. Сумма выставленных счетов завышает ожидание: часть уже оплачена. Соберём платёжный календарь в SQLite, сохраняя просроченные суммы отдельно от будущих поступлений. Это позволит обсуждать и кассовый план, и работу с должниками.
Счёт и платёж имеют разную детализацию
Одна строка invoices — счёт с суммой и договорной датой оплаты. В payments несколько поступлений могут относиться к одному счёту. Сначала агрегируем платежи, затем присоединяем сумму к счёту. Иначе его номинал повторится по числу оплат. Платежи учитываются только на дату снимка; будущие записи не должны уменьшать сегодняшний долг.
В примере снимок составлен на 5 октября. Счёт 1 на 1000 частично оплачен на 400 и ожидается 7 октября. Счёт 2 на 500 должен был быть оплачен 1 октября, а счёт 3 на 800 полностью закрыт. Счёт 4 на 700 ожидается 15 октября. Все суммы даны в одной валюте и в целых единицах.
Как разделяем временные корзины
Суммы со сроком раньше даты снимка относим к overdue. Для остальных вычисляем понедельник недели договорного срока через strftime с номером дня недели. В SQLite воскресенье имеет номер 0, поэтому выражение (weekday+6)%7 переводит его в смещение от понедельника. Вычитание этого числа дней даёт начало нужной недели.
Остаток равен номиналу минус подтверждённые платежи на дату снимка. Полностью закрытые счета не попадают в календарь. Отрицательный остаток следует вывести отдельной проверкой переплат: скрыто обрезать его до нуля перед финансовой сверкой нельзя. В примере переплат нет, поэтому финальный фильтр выделяет положительные остатки.
WITH invoices(id,due,amount) AS (
VALUES (1,'2026-10-07',1000),(2,'2026-10-01',500),
(3,'2026-10-08',800),(4,'2026-10-15',700)
), payments(invoice_id,paid_at,amount) AS (
VALUES (1,'2026-10-02',400),(3,'2026-10-03',300),(3,'2026-10-04',500)
), paid AS (
SELECT invoice_id,SUM(amount) AS amount FROM payments
WHERE paid_at<='2026-10-05' GROUP BY invoice_id
), balances AS (
SELECT i.id,i.due,i.amount-COALESCE(p.amount,0) AS remaining
FROM invoices i LEFT JOIN paid p ON p.invoice_id=i.id
), buckets AS (
SELECT *,CASE WHEN due<'2026-10-05' THEN 'overdue'
ELSE date(due,'-'||((CAST(strftime('%w',due) AS INTEGER)+6)%7)||' days') END AS bucket
FROM balances WHERE remaining>0
)
SELECT bucket,SUM(remaining) AS expected_amount
FROM buckets GROUP BY bucket ORDER BY bucket;Ожидаемые строки результата в порядке столбцов SELECT:
[["2026-10-05", 600], ["2026-10-12", 700], ["overdue", 500]]Что означают суммы календаря
На неделю 5 октября приходится 600, на неделю 12 октября — 700. Ещё 500 уже просрочены. Сумма открытых остатков равна 1800; она совпадает с 3000 выставленных минус 1200 полученных. Счёт 3 исчез из будущих ожиданий, потому что его две оплаты полностью закрыли обязательство.
Просроченные 500 не стоит автоматически включать в ближайшую неделю. Договорный срок уже не описывает ожидаемую дату поступления. Для реалистичного кассового плана нужна подтверждённая дата от клиента либо отдельная вероятностная оценка. Сохраняйте договорный срок и обновлённое ожидание в разных полях.
Что передать менеджерам
За каждой недельной суммой должен стоять список счетов с клиентом, остатком и сроком. По нему менеджер подтверждает ожидаемую оплату, а финансовая команда уточняет план. Итоговый календарь обновляется после новых поступлений, но закрытые версии полезно хранить для сравнения ожиданий с фактом.
Для нескольких валют стройте отдельные календарные суммы или применяйте согласованный курс. Дополнительно проверьте распределение платежей: нераспознанное поступление без invoice_id не уменьшит конкретный счёт, хотя деньги уже на банковском счёте. Такую сумму нужно показывать отдельной строкой сверки, чтобы календарь долгов и фактический остаток денег не противоречили друг другу.
Для работы с уже просроченными суммами пригодится другой разрез того же долга: Возраст дебиторской задолженности в SQL: как учесть частичную оплату.
Следующий уровень SQL-практики
В «Продвинутом SQL» вы учитесь агрегировать источники до соединения, сверять отчёты и решать финансовые кейсы. Запросы выполняются на SQLite и проходят автоматические проверки на разных входах. Практика завершается цельными тестовыми заданиями и аналитической витриной магазина. Откройте программу «Продвинутого SQL».
Продолжить на практике
- Продвинутый SQL: сложные запросы и задачи для собеседований ↗
Продолжение базового SQL: сложные JOIN, оконные рамки, временные ряды, retention, воронки и рекурсивные CTE. Разбирайте примеры и пишите запросы с автоматической проверкой — от отдельных расчётов до витрины интернет-магазина.
