Как перенести сальдо на следующий день в Sybase SQL
При работе с финансовыми данными в Sybase (SQL Anywhere, Adaptive Server) часто требуется рассчитать ежедневное сальдо по счету и, если оно положительное, перенести его на следующий день. В этой статье разберём, как реализовать такой перенос с помощью оконных функций и рекурсивных запросов.
Задача: сальдо и перенос на следующий день
Исходные данные - таблица с колонками: дата, дебет, кредит. Нужно:
- Рассчитать сальдо на каждый день (дебет минус кредит);
- Если сальдо больше нуля, перенести его на следующий день как начальный остаток;
- Если сальдо отрицательное или равно нулю, ничего не переносить.
Пример ожидаемого результата:
дата | деб | кред | сальдо
1 | 10 | 5 | 5
2 | 5 | 0 | 5
3 | 5 | 5 | 0
4 | 0 | 3 | 0Решение с помощью оконной функции SUM
Первый способ - использовать оконную функцию SUM с условием. Считаем накопительное сальдо, но сбрасываем его, когда оно становится отрицательным или нулевым:
SELECT
дата,
дебет,
кредит,
CASE
WHEN SUM(дебет - кредит) OVER (ORDER BY дата ROWS UNBOUNDED PRECEDING) <= 0
THEN 0
ELSE SUM(дебет - кредит) OVER (ORDER BY дата ROWS UNBOUNDED PRECEDING)
END AS сальдо
FROM таблица
ORDER BY дата;Этот запрос показывает сальдо, но не переносит его на следующий день. Для переноса потребуется рекурсия.
Рекурсивный запрос для переноса сальдо
Используем обобщённое табличное выражение (CTE) с рекурсией:
WITH RECURSIVE сальдо_cte AS (
SELECT
дата,
дебет,
кредит,
CASE
WHEN (дебет - кредит) > 0 THEN (дебет - кредит)
ELSE 0
END AS перенос
FROM таблица
WHERE дата = (SELECT MIN(дата) FROM таблица)
UNION ALL
SELECT
t.дата,
t.дебет,
t.кредит,
CASE
WHEN (c.перенос + t.дебет - t.кредит) > 0
THEN (c.перенос + t.дебет - t.кредит)
ELSE 0
END
FROM таблица t
JOIN сальдо_cte c ON t.дата = DATEADD(day, 1, c.дата)
)
SELECT * FROM сальдо_cte ORDER BY дата;Здесь c.перенос - это остаток с предыдущего дня, который добавляется к текущему дебету и кредиту. Если итог положительный - переносим, иначе обнуляем.
Альтернатива: временная таблица и курсор
Если рекурсивные CTE не поддерживаются (например, в старых версиях Sybase ASE), можно использовать курсор или временную таблицу. Пример с временной таблицей:
- Создаём временную таблицу с колонкой для переноса;
- Проходим по дням, обновляя перенос на основе предыдущей записи;
- Запросом выбираем итоговое сальдо.
Этот метод менее элегантен, но работает в любом диалекте.
Проверка и отладка
Перед внедрением проверьте запрос на тестовых данных. Убедитесь, что:
- Даты идут без пропусков (или используйте календарную таблицу);
- Перенос не накапливает ошибки округления;
- При отрицательном сальдо перенос действительно обнуляется.
Если нужно выводить только дни с положительным сальдо, добавьте WHERE сальдо > 0 в финальный SELECT.