Как программно сгруппировать строки запроса и заполнить табличную часть одного документа в 1С?

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

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

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

Метод 1: Использование итогов в запросе и обход по группировкам (Рекомендуемый подход)

Этот метод считается «золотым стандартом» разработки в 1С. Разберем подробнее, как он работает. Чтобы разделить данные на уровень шапки и уровень табличной части, нам нужно сгруппировать результат самого запроса. Для этого в тексте запроса используется конструкция ИТОГИ ПО.

В самом конце текста запроса (после секции УПОРЯДОЧИТЬ ПО) необходимо добавить подведение итогов по тому полю, которое будет определять уникальность создаваемого документа. Например, если мы группируем документы по оборудованию, мы пишем:

ИТОГИ ПО ОборудованиеСсылка

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


РезультатЗапроса = Запрос.Выполнить();
// Выбираем только верхний уровень (группировки по оборудованию)
ВыборкаОборудование = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаОборудование.Следующий() Цикл
    
    // 1. Создаем один документ на текущее уникальное Оборудование
    Демонтаж = Документы.ИзменениеПараметровОборудования.СоздатьДокумент();
    Демонтаж.Дата = ТекущаяДата();
    // Заполнение остальных реквизитов шапки из ВыборкаОборудование
    Демонтаж.Оборудование = ВыборкаОборудование.ОборудованиеСсылка;
    
    // 2. Получаем детальные записи, относящиеся ТОЛЬКО к этому оборудованию
    ВыборкаДетали = ВыборкаОборудование.Выбрать(); 
    
    Пока ВыборкаДетали.Следующий() Цикл
        // 3. Заполняем табличную часть "Пломбы"
        Док = Демонтаж.Пломбы.Добавить();
        Док.Пломба = ВыборкаДетали.ПломбаСсылка;
        // Заполнение остальных реквизитов табличной части
    КонецЦикла;
    
    // 4. Записываем сформированный документ со всеми строками
    Демонтаж.Записать(РежимЗаписиДокумента.Проведение);

КонецЦикла;

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

Метод 2: Сравнение с предыдущей строкой (Алгоритм Control Break)

Если по каким-то причинам вы не можете изменить текст запроса и добавить итоги, рассмотрим второй подход. Он базируется на сравнении текущего значения с предыдущим. В этом случае результат запроса обязательно должен быть отсортирован по ключевому полю (используйте УПОРЯДОЧИТЬ ПО ОборудованиеСсылка).

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


ПредыдущееОборудование = Неопределено;
Демонтаж = Неопределено;

Выборка = Запрос.Выполнить().Выбрать();

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

// ВАЖНО: После выхода из цикла нужно обязательно записать самый последний созданный документ!
Если Демонтаж <> Неопределено Тогда
    Демонтаж.Записать(РежимЗаписиДокумента.Проведение);
КонецЕсли;

Метод 3: Использование Таблицы Значений

Проанализируем еще один альтернативный подход, который часто применяют, когда данные приходят не напрямую из базы данных, а загружаются из внешнего файла (например, Excel), либо когда логика предварительной обработки слишком сложна для одного запроса. В таких случаях мы работаем с объектом ТаблицаЗначений.

Разберем по шагам:

  1. Выгружаем результат исходных данных в таблицу: ТЗДанных = Запрос.Выполнить().Выгрузить();
  2. Создаем копию этой таблицы, оставляя только ключевое поле (например, оборудование), и сворачиваем её методом Свернуть. Мы получаем уникальный список документов, которые нужно создать.
  3. Организуем цикл по этой свернутой таблице. Создаем документ-шапку.
  4. Внутри цикла ищем все подчиненные строки в исходной ТЗДанных с помощью метода НайтиСтроки, передавая структуру поиска с ключевым полем.
  5. Обходим найденный массив строк и добавляем их в табличную часть созданного документа.

Дополнительные рекомендации по оптимизации процессов массового создания документов

Создание документов в цикле — ресурсоемкая операция. Если выборка данных большая, процесс будет работать медленно и может заблокировать работу других пользователей. Рассмотрим подробнее, как ускорить код и сделать его надежнее:

  1. Использование транзакций: Оборачивайте блоки создания документов (например, по 50-100 штук) в конструкцию НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Внутри цикла используйте блок Попытка ... Исключение ... ОтменитьТранзакцию(). Это ускорит запись в базу данных и предотвратит появление "битых" данных, если на одном из документов возникнет ошибка проведения.
  2. Обработка заполнения по умолчанию: При программном создании не отрабатывают события модуля объекта, такие как ОбработкаЗаполнения, которые обычно заполняют валюты, ответственных и другие реквизиты по умолчанию. Имеет смысл явно вызывать метод Демонтаж.Заполнить(Неопределено) перед тем, как вы начнете присваивать реквизиты из выборки.
  3. Отключение проверок (при разовых переносах данных): Если вы пишете служебную обработку для загрузки исторических данных, и уверены в их корректности, перед записью установите свойство Демонтаж.ОбменДанными.Загрузка = Истина. Это отключит проверки при записи и существенно ускорит работу алгоритма.

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

← На главную