При работе в 1С часто возникает задача обработать данные из множества документов. Например, выгрузить в текстовый файл все товарные позиции из документов "Реализация товаров и услуг" за определенный период со статусом "В отборе".
Первая мысль, которая может прийти в голову, — это написать цикл, который будет отбирать нужные документы, программно открывать форму каждого из них, считывать данные из табличной части, а затем закрывать форму. Разберемся, почему этот подход неэффективен и как сделать эту операцию правильно и быстро.
Идея программного открытия формы или получения объекта документа для каждой записи кажется логичной, но она ведет к серьезным проблемам с производительностью, особенно на больших объемах данных. Проанализируем, почему этот метод не подходит для массовой обработки.
ПолучитьФорму() или получение полного объекта документа через Ссылка.ПолучитьОбъект() — это отдельный запрос к базе данных. Если вам нужно обработать 1000 документов, вы создадите 1000 отдельных запросов, что крайне неэффективно.ПолучитьОбъект() система считывает из базы данных все данные документа: все реквизиты шапки и все его табличные части. Если вам нужны только несколько колонок из одной табличной части, вы все равно загружаете в память огромный объем ненужной информации.В ходе обсуждения на форуме был приведен пример ошибочного подхода, который иллюстрирует эту проблему. Попытка получить данные через форму выглядит так:
// Неправильный и медленный подход!
Форма = ПолучитьФорму("Документ.ЗаказПокупателя.Форма.ФормаДокумента", Новый Структура("Ключ", Выборка.Ссылка));
// В данном примере даже ошибка в коде, правильно было бы Форма.Объект.Товары,
// но сам метод использования формы для чтения данных неверен.
Для Каждого ТекущаяСтрока из Форма.ссылка.Товары Цикл
// ... обработка строки
КонецЦикла;
Важно: Метод ПолучитьФорму() предназначен для интерактивной работы пользователя, а не для фоновой обработки данных. Для задач чтения и анализа данных всегда следует использовать запросы.
Самый правильный и производительный способ получить данные из табличных частей множества документов — это выполнить один запрос напрямую к нужной табличной части, установив отбор по реквизитам родительского документа.
Рассмотрим ключевой принцип. У каждой строки табличной части есть системное поле Ссылка, которое указывает на ее документ-владелец. Через это поле мы можем обращаться к любым реквизитам шапки документа и использовать их в условии ГДЕ.
Посмотрим на пример запроса, который решает нашу задачу — получить данные из табличной части "Товары" только для тех документов, у которых реквизит "Статус" равен "В отборе":
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РеализацияТоваровУслугТовары.Ссылка КАК Документ,
| РеализацияТоваровУслугТовары.Номенклатура,
| РеализацияТоваровУслугТовары.Количество,
| РеализацияТоваровУслугТовары.Цена,
| РеализацияТоваровУслугТовары.Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
|ГДЕ
| РеализацияТоваровУслугТовары.Ссылка.Статус = &Статус
| И РеализацияТоваровУслугТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
Запрос.УстановитьПараметр("Статус", Перечисления.СтатусыРеализаций.ВОтборе);
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата()));
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Что здесь происходит:
Документ.РеализацияТоваровУслуг.Товары, а не к самому документу.ГДЕ мы фильтруем строки по реквизиту документа-владельца: РеализацияТоваровУслугТовары.Ссылка.Статус = &Статус. Это самый важный момент. Платформа 1С сама оптимизирует такой запрос и выполнит его максимально быстро.После выполнения запроса мы получаем выборку, которую нужно обойти в цикле. Если нам нужно обрабатывать данные в разрезе документов (например, для каждого нового документа создавать новую секцию в файле выгрузки), удобно использовать обход результата с группировкой.
Вместо одного большого цикла, где на каждой итерации нужно проверять, не изменился ли документ, можно использовать вложенные циклы. Это делает код более читаемым и структурированным.
Посмотрим на пример:
// ... текст запроса из предыдущего примера
РезультатЗапроса = Запрос.Выполнить();
// Используем обход с группировкой по полю "Документ"
ВыборкаПоДокументам = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
// Внешний цикл по документам
Пока ВыборкаПоДокументам.Следующий() Цикл
// Здесь можно выполнить действия для каждого нового документа
// Например, записать заголовок в файл
Сообщить("Обрабатывается документ: " + ВыборкаПоДокументам.Документ);
// Внутренний цикл по строкам табличной части текущего документа
ВыборкаПоСтрокам = ВыборкаПоДокументам.Выбрать();
Пока ВыборкаПоСтрокам.Следующий() Цикл
// Обрабатываем конкретную строку ТЧ
СтрокаДляВыгрузки = Строка(ВыборкаПоСтрокам.Номенклатура) + ";" +
Строка(ВыборкаПоСтрокам.Количество) + ";" +
Строка(ВыборкаПоСтрокам.Цена);
// ... запись строки в файл ...
КонецЦикла;
КонецЦикла;
Этот подход, часто называемый "обработкой в два цикла", является стандартной практикой для задач, где требуется сгруппировать данные из плоской выборки. Он позволяет четко разделить логику обработки заголовков (документов) и деталей (строк табличной части).
В заключение, для массовой обработки данных из табличных частей документов по условиям всегда используйте прямые запросы. Это обеспечивает максимальную производительность, минимизирует нагрузку на сервер и является профессиональным стандартом разработки в 1С. Подход с получением объектов или открытием форм следует оставить для интерактивных сценариев или редких случаев, когда необходимо вызвать экспортные процедуры из модуля объекта документа.