Как исправить ошибку Поле объекта не обнаружено (Товары) при выборке табличной части в запросе

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При разработке печатных форм или отчетов в системе 1С программисты часто сталкиваются с необходимостью получения данных из табличных частей документов. Одним из способов является использование вложенных запросов непосредственно в блоке ВЫБРАТЬ. Однако такая конструкция таит в себе несколько нюансов, игнорирование которых приводит к ошибке «Поле объекта не обнаружено (Товары)». В этой статье мы подробно разберем, почему возникает эта ошибка, как правильно работать с вложенными таблицами и какие альтернативные методы получения данных существуют.

Анализ исходной ситуации и поиск причины ошибки

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

Выясним причину ошибки, проанализировав типы данных. В контексте печати переменная ДокументПечати часто является Массивом ссылок или одиночной Ссылкой на документ.

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

Проанализируем предоставленный код обхода выборки. Автор использует два цикла Пока Шапка.Следующий() и внутри него Пока ВыборкаСтрок.Следующий(). Ошибка возникает тогда, когда программист пытается «смешать» обращение к результату запроса и прямое обращение к объекту документа, который передан в функцию в виде массива.

Правильный обход вложенной таблицы в запросе

Когда мы выбираем табличную часть во вложенной выборке запроса, поле Товары в результате запроса становится объектом типа ВыборкаИзРезультатаЗапроса. К нему нельзя обращаться через Для каждого, так как это не коллекция, а итератор. Его нужно обходить строго через метод Выбрать().

Рассмотрим правильную структуру кода для обработки таких данных:


// Получаем результат запроса
РезультатЗапроса = Запрос.Выполнить();

// Начинаем выборку по верхнему уровню (шапка документа)
ВыборкаШапка = РезультатЗапроса.Выбрать();

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

Важный момент: если вы по ошибке напишете Для каждого Стр Из ВыборкаШапка.Товары, система выдаст ошибку, так как объект выборки не поддерживает обход по коллекции.

Решение проблемы через «плоский» запрос (Левое Соединение)

Использование вложенных таблиц в запросах 1С (конструкция Документ.Товары.(...)) считается менее производительным и имеет ряд ограничений. Например, в таких запросах нельзя использовать группировки (СГРУППИРОВАТЬ ПО) или итоги. Проанализируем более профессиональный подход — использование левого соединения шапки с табличной частью.

Рассмотрим, как изменить запрос для получения «плоской» таблицы:


ВЫБРАТЬ
    ЗаказКлиента.Ссылка КАК Ссылка,
    ЗаказКлиента.Номер КАК Номер,
    ЗаказКлиента.Дата КАК Дата,
    ЗаказКлиентаТовары.Номенклатура КАК Номенклатура,
    ЗаказКлиентаТовары.Количество КАК Количество,
    ЗаказКлиентаТовары.Сумма КАК Сумма
ИЗ
    Документ.ЗаказКлиента КАК ЗаказКлиента
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
        ПО ЗаказКлиента.Ссылка = ЗаказКлиентаТовары.Ссылка
ГДЕ
    ЗаказКлиента.Ссылка В(&МассивОбъектов)
УПОРЯДОЧИТЬ ПО
    Ссылка,
    ЗаказКлиентаТовары.НомерСтроки
ИТОГИ ПО
    Ссылка

В данном случае мы используем ИТОГИ ПО Ссылка. Это позволяет нам обходить результат запроса иерархически, что идеально подходит для печати документов. Посмотрим на пример кода обхода такого результата:


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

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

Почему «плоский» запрос лучше?

Разберем преимущества использования ЛЕВОЕ СОЕДИНЕНИЕ и обхода по группировкам:

  1. Производительность: СУБД (SQL Server, PostgreSQL) гораздо эффективнее обрабатывает соединения таблиц, чем иерархические вложенные выборки.
  2. Гибкость: Вы можете легко добавлять условия на поля табличной части, использовать агрегатные функции (СУММА, МАКСИМУМ) и группировки.
  3. Отсутствие ошибок типов: Вам не нужно беспокоиться о том, является ли поле Товары выборкой или чем-то еще. Вы всегда работаете с обычным результатом запроса.

Технические нюансы работы с типами данных

Вернемся к ошибке автора темы. Если вы получили сообщение Поле объекта не обнаружено (Товары), проверьте следующее:

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

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

← На главную