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