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