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