Как перенести сальдо на следующий день в 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), можно использовать курсор или временную таблицу. Пример с временной таблицей:

    1. Создаём временную таблицу с колонкой для переноса;
    2. Проходим по дням, обновляя перенос на основе предыдущей записи;
    3. Запросом выбираем итоговое сальдо.

    Этот метод менее элегантен, но работает в любом диалекте.

    Проверка и отладка

    Перед внедрением проверьте запрос на тестовых данных. Убедитесь, что:

    • Даты идут без пропусков (или используйте календарную таблицу);
    • Перенос не накапливает ошибки округления;
    • При отрицательном сальдо перенос действительно обнуляется.

    Если нужно выводить только дни с положительным сальдо, добавьте WHERE сальдо > 0 в финальный SELECT.

    Часто задаваемые вопросы