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