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