Как объединить подневные строки таблицы значений в непрерывные периоды по условию в 1С?

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

При расчете взаиморасчетов, начислений или процентов разработчики часто сталкиваются с ситуацией, когда исходные данные разбиты подневно: каждая строка содержит один день (например, с 01.10 по 01.10, с 02.10 по 02.10 и так далее). Для вывода отчетов или дальнейшей обработки требуется «свернуть» эти дни в укрупненные интервалы, пока значение ключевого реквизита (например, суммы долга, ставки или признака оплаты) остается неизменным, и начинать новый интервал только в момент изменения.

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

Почему не работает стандартный метод ТаблицаЗначений.Свернуть()?

Метод ТаблицаЗначений.Свернуть() группирует все строки по указанным колонкам по всей таблице без учета хронологии. Проанализируем ситуацию: если сумма долга составляла 10 000 рублей с 1 по 5 число, затем 6 числа изменилась на 15 000 рублей, а 10 числа снова стала 10 000 рублей, стандартное сворачивание объединит данные за 1–5 и 10–15 числа в одну общую строку. Это приведет к полной потере хронологической последовательности интервалов.

Именно поэтому для сохранения непрерывных цепочек дат требуется последовательный обход строк и фиксация момента смены значений.

Решение 1. Последовательный обход таблицы значений с запоминанием состояния

Разберем классический и наиболее простой алгоритм сжатия интервалов через обход исходной таблицы. Основная идея заключается в том, что мы последовательно читаем строки: если ключевые реквизиты совпадают с предыдущей строкой, мы просто продлеваем дату окончания в текущей результирующей строке. Если же реквизиты изменились (или это первая строка), мы создаем новую строку в результирующей таблице.

Посмотрим на пример программного кода:


// Предположим, что ИсходнаяТЗ содержит колонки: ДатаНачала, ДатаОкончания, СуммаДолга, Контрагент

// 1. Обязательно гарантируем хронологический порядок
ИсходнаяТЗ.Сортировать("ДатаНачала Возр");

// 2. Создаем структуру результирующей таблицы
СвернутаяТЗ = ИсходнаяТЗ.СкопироватьКолонки();

// Переменные для отслеживания изменения контролируемых значений
ТекущаяСуммаДолга = Неопределено;
ТекущийКонтрагент = Неопределено;
СвернутаяСтрока = Неопределено;

// 3. Последовательно обходим исходные строки
Для Каждого СтрокаТЗ Из ИсходнаяТЗ Цикл
    
    // Проверяем, изменились ли ключевые значения по сравнению с предыдущей строкой
    Если СтрокаТЗ.СуммаДолга <> ТекущаяСуммаДолга ИЛИ СтрокаТЗ.Контрагент <> ТекущийКонтрагент Тогда
        
        // Значение изменилось: фиксируем начало нового периода
        СвернутаяСтрока = СвернутаяТЗ.Добавить();
        ЗаполнитьЗначенияСвойств(СвернутаяСтрока, СтрокаТЗ);
        
        ТекущаяСуммаДолга = СтрокаТЗ.СуммаДолга;
        ТекущийКонтрагент = СтрокаТЗ.Контрагент;
        
    Иначе
        
        // Значение не менялось: продлеваем дату окончания текущего периода
        СвернутаяСтрока.ДатаОкончания = СтрокаТЗ.ДатаОкончания;
        
    КонецЕсли;
    
КонецЦикла;

Разберем по шагам ключевые детали данного алгоритма:

  1. Предварительная сортировка: перед циклом коллекцию обязательно нужно упорядочить по возрастанию даты (ТаблицаЗначений.Сортировать()), иначе смежные дни окажутся в разных частях таблицы.
  2. Хранение ссылки на строку: переменная СвернутаяСтрока удерживает ссылку на последнюю добавленную строку в СвернутаяТЗ, что позволяет напрямую изменять ее реквизит ДатаОкончания на каждой следующей итерации без дополнительных поисков.
  3. Инициализация значений: использование Неопределено в начальных переменных гарантирует, что самая первая строка всегда будет распознана как начало нового интервала.

Решение 2. Обход таблицы значений по индексам

Если требуется сравнивать строку напрямую со следующей или предыдущей строкой, можно использовать цикл по индексам. Выясним, как это реализовать:


ВсегоСтрок = ИсходнаяТЗ.Количество();
Для Индекс = 0 По ВсегоСтрок - 1 Цикл
    
    СтрокаТекущая = ИсходнаяТЗ[Индекс];
    
    // Проверяем предыдущую строку
    Если Индекс > 0 Тогда
        СтрокаПредыдущая = ИсходнаяТЗ[Индекс - 1];
        // Дополнительная логика проверки разрывов в датах
    КонецЕсли;
    
    // Проверяем следующую строку
    Если Индекс < ВсегоСтрок - 1 Тогда
        СтрокаСледующая = ИсходнаяТЗ[Индекс + 1];
    КонецЕсли;
    
КонецЦикла;

Обратите внимание: если между датами есть пропуски (например, после 05.10 сразу идет 10.10), алгоритм должен проверять не только равенство сумм, но и непрерывность дней (СтрокаТекущая.ДатаНачала = СтрокаПредыдущая.ДатаОкончания + 86400), чтобы случайно не объединить разорванные во времени периоды.

Решение 3. Оптимизация на уровне запроса

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

  1. Виртуальные таблицы регистров: если данные выбираются из регистра накопления (например, взаиморасчетов), эффективнее использовать виртуальную таблицу ОстаткиИОбороты с периодичностью Регистратор или Авто. Запрос сразу вернет только точки изменения данных, исключая необходимость создавать и обрабатывать промежуточные дни.
  2. Интервальный запрос через соединение: дату окончания каждого интервала можно определить левым соединением таблицы событий с самой собой по условию Таблица1.Дата < Таблица2.Дата с группировкой и вычислением МИНИМУМ(Таблица2.Дата).
  3. Оконные функции: в современных версиях платформы 1С для получения даты следующего изменения суммы можно использовать аналитическую функцию LEAD(ДатаНачала, 1, &КонецПериода) OVER (ORDER BY ДатаНачала).
← На главную