Как обработать табличные части документов по условию, не открывая их формы?

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

При работе в 1С часто возникает задача обработать данные из множества документов. Например, выгрузить в текстовый файл все товарные позиции из документов "Реализация товаров и услуг" за определенный период со статусом "В отборе".

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

Почему не нужно открывать форму или получать объект для чтения данных?

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

  1. Избыточные обращения к серверу. Каждое открытие формы через ПолучитьФорму() или получение полного объекта документа через Ссылка.ПолучитьОбъект() — это отдельный запрос к базе данных. Если вам нужно обработать 1000 документов, вы создадите 1000 отдельных запросов, что крайне неэффективно.
  2. Чтение лишних данных. При вызове ПолучитьОбъект() система считывает из базы данных все данные документа: все реквизиты шапки и все его табличные части. Если вам нужны только несколько колонок из одной табличной части, вы все равно загружаете в память огромный объем ненужной информации.
  3. Низкая скорость. Совокупность этих факторов приводит к тому, что обработка занимает значительно больше времени по сравнению с другими методами.

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


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

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

Оптимальный способ: прямой запрос к табличной части

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

Рассмотрим ключевой принцип. У каждой строки табличной части есть системное поле Ссылка, которое указывает на ее документ-владелец. Через это поле мы можем обращаться к любым реквизитам шапки документа и использовать их в условии ГДЕ.

Посмотрим на пример запроса, который решает нашу задачу — получить данные из табличной части "Товары" только для тех документов, у которых реквизит "Статус" равен "В отборе":


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    РеализацияТоваровУслугТовары.Ссылка КАК Документ,
    |    РеализацияТоваровУслугТовары.Номенклатура,
    |    РеализацияТоваровУслугТовары.Количество,
    |    РеализацияТоваровУслугТовары.Цена,
    |    РеализацияТоваровУслугТовары.Сумма
    |ИЗ
    |    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
    |ГДЕ
    |    РеализацияТоваровУслугТовары.Ссылка.Статус = &Статус
    |    И РеализацияТоваровУслугТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода";

Запрос.УстановитьПараметр("Статус", Перечисления.СтатусыРеализаций.ВОтборе);
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата()));

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

Что здесь происходит:

Обработка результата запроса: обход с группировкой

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

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

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


// ... текст запроса из предыдущего примера

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

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

Этот подход, часто называемый "обработкой в два цикла", является стандартной практикой для задач, где требуется сгруппировать данные из плоской выборки. Он позволяет четко разделить логику обработки заголовков (документов) и деталей (строк табличной части).

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

← На главную