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