Денежный поток в SQL: переводы между своими счетами не должны стать доходом
Компания перевела деньги с расчётного счёта на свой накопительный. В выгрузке есть поступление, но нового дохода у бизнеса не возникло. Если смешать внутренние переводы с оплатами клиентов и поставщиков, обороты денежного потока вырастут. Для отчёта нужен явный периметр собственных счетов и классификация каждого движения.
Определите, чьи деньги описывает отчёт
Составьте справочник собственных счетов. Для каждой операции нужны счёт отправителя, счёт получателя, положительная сумма и валюта. В примере одно движение хранится одной строкой, а все суммы выражены в одной валюте. Если банковская выгрузка содержит обе стороны перевода отдельно, сначала сопоставьте их по устойчивому идентификатору операции.
Не используйте только текст назначения платежа для определения внутреннего перевода. Формулировки различаются, а одинаковое слово может встретиться у внешнего контрагента. Принадлежность счетов даёт проверяемое основание.
Разберите четыре направления
Оба счёта собственные — внутреннее перемещение. Только получатель собственный — внешний приток. Только отправитель собственный — внешний отток. Ни один не принадлежит компании — операция вне выбранного периметра. Отдельно сохраните неполные сведения о стороне операции.
В запросе SQLite принадлежность определяется через EXISTS, поэтому справочник не размножает строки операции. Для отсутствующего номера счёта выбирается unresolved: неизвестная сторона не должна автоматически считаться внешним контрагентом. Для другого источника может быть отдельный подтверждённый признак внешней стороны.
WITH own(account) AS (VALUES ('our_A'),('our_B')),
tx(id,src,dst,amount) AS (
VALUES (1,'our_A','our_B',1000),(2,'client','our_A',500),
(3,'our_A','vendor',200),(4,'our_A','bank',5),
(5,'our_A',NULL,50),(6,'other_1','other_2',20)
), flags AS (
SELECT *,EXISTS(SELECT 1 FROM own WHERE account=src) AS own_src,
EXISTS(SELECT 1 FROM own WHERE account=dst) AS own_dst FROM tx
), classified AS (
SELECT *,CASE WHEN src IS NULL OR dst IS NULL THEN 'unresolved'
WHEN own_src AND own_dst THEN 'internal' WHEN own_dst THEN 'income'
WHEN own_src THEN 'expense' ELSE 'outside' END AS kind FROM flags
)
SELECT kind,COUNT(*) AS operations,
SUM(CASE kind WHEN 'income' THEN amount WHEN 'expense' THEN -amount
WHEN 'unresolved' THEN NULL ELSE 0 END) AS external_net,
SUM(amount) AS raw_amount
FROM classified GROUP BY kind ORDER BY kind;
Посмотрите обороты и исключения
В примере внутренний перевод на 1000 даёт нулевой внешний поток. Поступление клиента — +500, платёж поставщику и комиссия — вместе −205. Чистый классифицированный внешний поток равен +295. Операция на 50 с неизвестным получателем остаётся в очереди разбора.
Комиссия по внутреннему переводу — отдельный внешний расход. Не удаляйте её вместе с переводом: деньги ушли внешнему банку. Если комиссия включена в одну строку, сначала разделите движение на части по правилам источника.
Сверьте отчёт с изменением денег
Для полного изменения остатка учитываются все собственные счета, начальный и конечный баланс, а также незакрытые операции. Внутренние переводы взаимно погашаются в периметре компании, но меняют остатки отдельных счетов. Отчёт по одному счёту и консолидированный cashflow отвечают на разные вопросы.
Для валютных операций задайте валюту отчёта и курс пересчёта. Курсовая переоценка остатка не является обычным банковским поступлением. Не складывайте рубли и доллары в одну сумму до согласованного преобразования.
В приёмке нужны собственный перевод, внешнее поступление, внешний платёж, комиссия, неизвестная сторона и операция вне периметра. Сумма классифицированных групп должна объясняться исходными движениями, а список unresolved — быть видимым ответственному.
При добавлении нового собственного счёта пересмотрите классификацию истории на согласованную дату. Раньше перевод на него мог ошибочно считаться расходом. Справочник периметра и его версия поэтому являются частью определения показателя, а не только вспомогательными данными запроса.
От запроса к рабочей витрине
В курсе вы разберёте финансовые отчёты, сложные соединения и проверки качества данных. Запросы нужно будет проверить на дублях и неполных данных, затем собрать итоговую витрину. Это помогает связать классификацию денежных движений с воспроизводимой сверкой и видимыми исключениями.
Откройте программу платного курса «Продвинутый SQL: сложные запросы и задачи для собеседований».
Продолжить разбор: UNION или UNION ALL: как случайно потерять одинаковые платежи.
Продолжить на практике
- Продвинутый SQL: сложные запросы и задачи для собеседований ↗
Продолжение базового SQL: сложные JOIN, оконные рамки, временные ряды, retention, воронки и рекурсивные CTE. Разбирайте примеры и пишите запросы с автоматической проверкой — от отдельных расчётов до витрины интернет-магазина.
