Рассмотрим частую и весьма интересную задачу, с которой сталкивается практически каждый разработчик 1С: распределение общей суммы (например, оплаты) по детализированным строкам (например, по строкам задолженностей). Проанализируем исходные данные проблемы, разберем возможные подводные камни и найдем самые оптимальные варианты ее решения от классических подходов до современных оконных функций.
Посмотрим на пример. У нас есть две таблицы. Первая содержит детализацию долгов (начислений) по строкам документа:
Вторая таблица содержит общую сумму оплаты по этому документу:
Нам необходимо получить таблицу остатков (неоплаченных сумм) по каждой строке, последовательно списывая оплату по правилу ФИФО (начиная с первой строки). Правильный результат должен выглядеть так:
Участники обсуждения справедливо отметили, что ключ к решению этой задачи — вычисление нарастающего итога по строкам. Давайте подробно разберем несколько способов реализации этой логики.
Исторически в 1С для получения нарастающего итога использовалось внутреннее или левое соединение таблицы с самой собой. Давайте посмотрим, как работает этот механизм.
Мы берем таблицу начислений и соединяем ее с ее же копией по условию, что номер строки во второй таблице меньше или равен номеру строки в первой таблице. Затем группируем результат и суммируем значения. Получив нарастающий итог (сколько всего должны на текущей строке), мы можем легко математически вычесть сумму оплаты.
Пример структуры классического запроса:
ВЫБРАТЬ
Таблица1.Ссылка,
Таблица1.НомерСтроки,
Таблица1.Сумма,
СУММА(ЕСТЬNULL(ТаблицаКопия.Сумма, 0)) КАК НарастающийИтог
ПОМЕСТИТЬ ВТ_СНарастающимИтогом
ИЗ
ВТ_Детализация КАК Таблица1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Детализация КАК ТаблицаКопия
ПО Таблица1.Ссылка = ТаблицаКопия.Ссылка
И Таблица1.НомерСтроки >= ТаблицаКопия.НомерСтроки
СГРУППИРОВАТЬ ПО
Таблица1.Ссылка,
Таблица1.НомерСтроки,
Таблица1.Сумма
Важное ограничение: У этого метода есть критический недостаток — экспоненциальное падение производительности (сложность O(N²)). Если в документе будут тысячи строк, базе данных придется строить тяжелое декартово произведение, и запрос будет выполняться недопустимо долго.
Если нам необходимо вывести результат в отчет без программного формирования наборов данных, мы можем воспользоваться мощными встроенными функциями СКД. В языке выражений системы компоновки данных существует специальная функция для расчета нарастающих итогов.
Разберем по шагам, как это настроить. В ресурсах или в вычисляемых полях схемы компоновки пропишем следующую формулу:
ВычислитьВыражение("Сумма(Сумма)", "Группировка", , "Первая", "Текущая")
В чем преимущество этого подхода? Платформа 1С сама выполнит линейный расчет (сложность O(N)) от первой строки группировки до текущей. Мы не утяжеляем SQL-запрос сложными соединениями, данные обрабатываются быстро и прозрачно. Получив это значение как ресурс НарастающийИтогСКД, мы прямо в СКД можем добавить вычисляемое поле для определения непогашенного остатка по строке с учетом поля Оплачено.
Начиная с версии платформы 1С:Предприятие 8.3.24, в языке запросов появились долгожданные аналитические оконные функции. Это настоящий прорыв для подобных задач. Теперь мы можем рассчитать нарастающий итог прямо в выборке без каких-либо левых соединений и потери производительности.
Посмотрим на синтаксис оконной функции для нашей задачи:
ВЫБРАТЬ
Таб1.Ссылка,
Таб1.НомерСтроки,
Таб1.Сумма КАК СуммаСтроки,
СУММА(Таб1.Сумма) ОВЕР (
ПАРТИЦИРОВАТЬ ПО Таб1.Ссылка
СОРТИРОВАТЬ ПО Таб1.НомерСтроки
) КАК НарастающийИтог
ИЗ
ТаблицаДолгов КАК Таб1
Конструкция ПАРТИЦИРОВАТЬ ПО изолирует расчет итога в рамках каждого отдельного документа (разрез), а СОРТИРОВАТЬ ПО обеспечивает правильный порядок сложения от первой строки к последней. После вычисления этого поля нам остается только левым соединением добавить сумму из таблицы оплат и вычислить остаток (вычитая оплату из нарастающего итога и корректируя результат в пределах нуля и суммы самой строки).
Часто бизнес требует рассчитывать просроченную задолженность по особым правилам, которые сильно отличаются от стандартного ФИФО. Например, нужно учитывать приоритеты договоров, сроки годности, частичные штрафы и пени.
В таких ситуациях профессиональные разработчики 1С рекомендуют отказываться от решения задачи исключительно инструментами запроса. Выясним причину: чрезмерно сложные запросы трудны в отладке, тяжело читаются и могут работать неоптимально.
Гораздо более производительный и гибкий подход:
ТаблицаЗначений или обходить через выборку.Этот метод работает максимально быстро (поскольку выполняется в оперативной памяти сервера приложений за один проход, алгоритм O(N)), легко отлаживается и позволяет внедрить сколь угодно сложную логику распределения сумм.