Почему запрос в процедуре ОбработкаПроведения возвращает ошибку или пустой результат?

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

В процессе разработки механизмов проведения документов в 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда запрос, прекрасно работающий в консоли, выдает «Ошибку чтения значения» в отладчике или возвращает нулевые остатки непосредственно в коде конфигурации. Рассмотрим причины возникновения этой проблемы на примере типичного фрагмента кода в процедуре ОбработкаПроведения.

Анализ синтаксической ошибки: Свойство против Метода

Проанализируем фрагмент кода, представленный автором в обсуждении. Одной из главных причин возникновения сообщения «Ошибка чтения значения» в отладчике является попытка обращения к несуществующим свойствам объектов. В коде автора мы видим следующую конструкцию:


Если Выборка.Количество > 1 Тогда
    Сообщить("Обнаружен дубликат");
    Отказ = Истина;
ИначеЕсли Выборка.Количество = 0 Тогда
    ПозицияВЗаказе.Количество = ТекСтрокаРезерва.Количество;
Иначе
    ПозицияВЗаказе.Количество = ТекСтрокаРезерва.Количество + Выборка["Количество"];
КонецЕсли;

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

Как исправить: Всегда используйте скобки при обращении к методам объектов. Кроме того, проверять результат запроса на наличие записей эффективнее с помощью метода Пустой() объекта РезультатЗапроса, не создавая лишнюю выборку.

Проблема Момента Времени и Границы при получении остатков

Проанализируем ситуацию, когда запрос возвращает пустой результат (0), хотя данные в регистре точно есть. В коде используется следующая установка параметра даты:

Запрос.УстановитьПараметр("Дата", Новый МоментВремени(Дата, Ссылка));

Выясним причину, почему это может привести к неверным результатам. При проведении документа (особенно при его перепроведении) важно понимать, на какой момент мы хотим получить остатки. Объект МоментВремени включает в себя и время, и ссылку на сам документ. Если документ уже имеет движения в регистре, виртуальная таблица Остатки на этот МоментВремени может учесть движения самого этого документа.

Если ваша задача — получить остатки ДО того, как текущий документ внесет свои изменения, необходимо использовать объект Граница с видом Исключая. Рассмотрим пример правильной установки параметра:


Запрос.УстановитьПараметр("Дата", Новый Граница(Новый МоментВремени(Дата, Ссылка), ВидГраницы.Исключая));

Это гарантирует, что запрос вернет состояние регистра «за мгновение» до записи движений текущим документом. Именно в этом часто кроется разница с консолью запросов, где пользователь вручную устанавливает дату или границу, не привязанную к контексту транзакции проведения.

Влияние существующих движений и режима удаления движений

Проанализируем еще один важный аспект: состояние движений документа в базе данных в момент выполнения запроса. Если в свойствах документа установлено «Удаление движений: Не удалять автоматически», то при перепроведении старые записи документа остаются в регистре до завершения процедуры ОбработкаПроведения.

Если мы выполняем запрос к остаткам внутри этой процедуры, он «видит» старые движения этого же документа. Рассмотрим, как это исправить:

  1. Перед выполнением запроса необходимо явно очистить и записать движения документа, если они могут повлиять на результат.
  2. Используйте метод Движения.ИмяРегистра.Записать() после очистки коллекции, чтобы зафиксировать пустое состояние в базе данных внутри транзакции.

Посмотрим на пример кода:


Движения.СкладРезервации.Записывать = Истина;
Движения.СкладРезервации.Очистить();
Движения.СкладРезервации.Записать(); // Теперь в базе пусто по этому документу
// ... далее выполняем запрос

Оптимизация: Избавляемся от запроса в цикле

Обратим внимание на критическую архитектурную ошибку в исходном коде: выполнение Новый Запрос внутри цикла Для Каждого ТекСтрокаРезерва Из УпакованнаяПродукция. Рассмотрим, почему это плохо:

Разберем правильный подход с использованием временных таблиц. Проанализируем шаги:

  1. Выгружаем табличную часть документа в Таблицу Значений.
  2. Передаем эту таблицу в запрос как параметр.
  3. Внутри запроса помещаем данные во временную таблицу.
  4. Соединяем временную таблицу с виртуальной таблицей Остатки по всем необходимым измерениям (Склад, Номенклатура и т.д.) за один раз.

Посмотрим на пример реализации такого подхода:


// 1. Подготовка данных
ТаблицаДляЗапроса = УпакованнаяПродукция.Выгрузить();

// 2. Текст запроса с временной таблицей
ТекстЗапроса = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ТЗ КАК ТЗ;
               |ВЫБРАТЬ
               | ВТ.ВидПродукции, ВТ.Месторождение, ВТ.Количество КАК ВДокументе,
               | ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК ОстатокНаСкладе
               |ИЗ ВТ_Данные КАК ВТ
               |ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СкладРезервации.Остатки(&Граница, ) КАК Остатки
               |ПО ВТ.ВидПродукции = Остатки.ВидПродукции И ВТ.Заказ = Остатки.Заказ";
               
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("ТЗ", ТаблицаДляЗапроса);
Запрос.УстановитьПараметр("Граница", Новый Граница(МоментВремени(), ВидГраницы.Исключая));

Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();

Пока Выборка.Следующий() Цикл
    // Обработка данных сразу для всех строк
КонецЦикла;

Заключение

Подводя итог, можно выделить три правила успешного запроса в процедуре проведения:

Во-первых, всегда используйте Границу с исключением, если вам нужны чистые остатки до совершения текущей операции. Во-вторых, проверяйте синтаксис обращения к результатам: Количество() — это функция, требующая скобок. В-третьих, никогда не пишите запросы внутри цикла. Используйте мощь временных таблиц и соединений для получения всех необходимых данных одним пакетом. Это не только решит проблему с «невидимыми» данными, но и на порядок ускорит работу вашей системы.

← На главную