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