Как реализовать сложный взаимозачет распределение многие ко многим в одном запросе 1С

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

Задача проведения взаимозачетов в рамках одного запроса без использования циклов Для каждого или постобработки в таблице значений является одной из самых сложных и интересных в практике программиста 1С. Ситуация осложняется, когда в одной таблице перемешаны должники и кредиторы, и нам необходимо «схлопнуть» их задолженности по принципу FIFO (First In, First Out), но в масштабе всей таблицы. Рассмотрим подробнее, как решить эту задачу, используя метод пересечения интервалов.

Суть проблемы и концепция решения

Когда мы имеем список долгов и список оплат (или встречных требований), нам нужно сопоставить каждую копейку из одной таблицы с каждой копейкой из другой. В обычном коде мы бы открыли два указателя на строки и двигались по ним, уменьшая остатки. В запросе 1С такая итерация невозможна, поэтому мы будем использовать математическую модель числовой оси.

Представим, что все долги — это отрезки, лежащие один за другим на одной бесконечной линии. Все кредиты (оплаты) — это такие же отрезки на другой линии. Задача взаимозачета сводится к тому, чтобы наложить одну линию на другую и найти места, где эти отрезки перекрываются. Проанализируем ситуацию: если отрезок долга пересекается с отрезком оплаты, значит, между ними возможен взаимозачет. Длина этого пересечения и будет суммой зачета.

Шаг 1: Подготовка данных и нумерация строк

Для начала нам необходимо разделить исходную таблицу на две временные таблицы: ВТ_Должники и ВТ_Кредиторы. В каждой таблице данные должны быть сгруппированы, чтобы один участник встречался в таблице один раз (или имел уникальный ключ). Поскольку в языке запросов 1С нет встроенной функции ROW_NUMBER(), нам нужно пронумеровать строки самостоятельно. Это необходимо для расчета нарастающего итога.

Разберем пример создания нумерации через соединение таблицы самой с собой:


ВЫБРАТЬ
    Т1.Участник КАК Участник,
    Т1.Сумма КАК Сумма,
    КОЛИЧЕСТВО(Т2.Участник) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_Должники_Нумерованная
ИЗ
    ВТ_Должники КАК Т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Должники КАК Т2
        ПО Т1.Участник >= Т2.Участник
СГРУППИРОВАТЬ ПО
    Т1.Участник,
    Т1.Сумма
;

Здесь мы используем поле Участник (или любое другое уникальное поле) для определения порядка. Чем больше «младших» записей найдено для текущей строки, тем выше ее порядковый номер.

Шаг 2: Расчет границ интервалов (нарастающий итог)

Теперь для каждой строки нам нужно определить две координаты на числовой оси: Начало и Конец. Координата Конец — это сумма текущей строки плюс суммы всех предыдущих. Координата Начало — это сумма только предыдущих строк (или Конец минус текущая Сумма).

Выясним причину важности этого шага: без этих границ мы не сможем применить условие пересечения отрезков. Рассмотрим пример расчета границ:


ВЫБРАТЬ
    Т1.НомерСтроки,
    Т1.Сумма,
    СУММА(Т2.Сумма) - Т1.Сумма КАК НачалоИнтервала,
    СУММА(Т2.Сумма) КАК КонецИнтервала
ПОМЕСТИТЬ ВТ_Долги_Интервалы
ИЗ
    ВТ_Должники_Нумерованная КАК Т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Должники_Нумерованная КАК Т2
        ПО Т1.НомерСтроки >= Т2.НомерСтроки
СГРУППИРОВАТЬ ПО
    Т1.НомерСтроки,
    Т1.Сумма
;

Аналогичные действия необходимо выполнить для таблицы кредиторов. В результате мы получим две таблицы, где каждая запись знает свой «участок» на общей шкале задолженности.

Шаг 3: Соединение таблиц по условию пересечения

Это самый ответственный этап. Мы должны соединить ВТ_Долги_Интервалы и ВТ_Кредиты_Интервалы. Условие связи в запросе должно находить только те пары строк, чьи интервалы имеют общие точки. Математически это выражается так: Начало1 < Конец2 И Начало2 < Конец1.

Проанализируем расчет итоговой суммы зачета. Сумма пересечения двух отрезков вычисляется как разница между минимальным из концов и максимальным из начал этих отрезков. Посмотрим на пример кода:


ВЫБРАТЬ
    Долги.Участник КАК Дебитор,
    Кредиты.Участник КАК Кредитор,
    // Формула: Минимум(Конец1, Конец2) - Максимум(Начало1, Начало2)
    ВЫБОР
        КОГДА Долги.КонецИнтервала < Кредиты.КонецИнтервала ТОГДА Долги.КонецИнтервала
        ИНАЧЕ Кредиты.КонецИнтервала
    КОНЕЦ - 
    ВЫБОР
        КОГДА Долги.НачалоИнтервала > Кредиты.НачалоИнтервала ТОГДА Долги.НачалоИнтервала
        ИНАЧЕ Кредиты.НачалоИнтервала
    КОНЕЦ КАК СуммаЗачета
ИЗ
    ВТ_Долги_Интервалы КАК Долги
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Кредиты_Интервалы КАК Кредиты
        ПО Долги.НачалоИнтервала < Кредиты.КонецИнтервала
        И Кредиты.НачалоИнтервала < Долги.КонецИнтервала
;

Особенности и ограничения метода

Несмотря на изящество, данный метод имеет свои нюансы, которые необходимо учитывать при разработке реальных систем на платформе 1С:Предприятие 8.3:

  1. Производительность: Использование самосоединений для расчета нарастающих итогов имеет квадратичную сложность O(n^2). На таблицах в несколько тысяч строк запрос может выполняться очень долго.
  2. Точность: При работе с числами с плавающей точкой (хотя в 1С используется Decimal) теоретически возможны копеечные расхождения из-за округлений, но при стандартных расчетах Сумма(15,2) этот алгоритм работает стабильно.
  3. Уникальность ключей: Для корректной нумерации строк крайне важно иметь уникальный ключ. Если у вас несколько одинаковых сумм по одному контрагенту, добавьте в ключ связи Ссылка документа или МоментВремени.

Разберем, почему эксперты часто советуют избегать таких запросов в пользу обхода коллекции на сервере. В языке 1С обход двух указателей в цикле выполняется за линейное время O(n), что значительно быстрее «тяжелого» SQL-запроса на больших объемах данных. Однако, если задача стоит выполнить всё строго в рамках одного Запроса (например, внутри сложной схемы компоновки данных или при жестком требовании отсутствия кода на встроенном языке), метод интервалов остается единственным надежным инструментом.

Таким образом, мы рассмотрели полный цикл превращения хаотичных записей о долгах в стройную таблицу взаимозачетов. Мы научились нумеровать строки, строить нарастающие итоги и использовать математику интервалов для распределения сумм.

← На главную