Почему в отладчике 1С возникает «Ошибка чтения значения» при работе с запросами и как это исправить?

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

Каждый начинающий разработчик в среде 1С:Предприятие рано или поздно сталкивается с ситуацией, когда при пошаговой отладке кода в табло или при наведении мыши на свойства выборки запроса система выдает обескураживающее сообщение: «Ошибка чтения значения». Это вызывает панику: в консоли запросов всё работает идеально, а в реальном коде данные как будто исчезают.

В этой статье мы подробно разберем, почему возникает эта ошибка, как устроен механизм выборки данных в 1С, почему нельзя путать параметры виртуальных таблиц с секцией ГДЕ, и как правильно анализировать результаты запросов в отладчике, чтобы не тратить часы на поиск несуществующих проблем.


Причина №1. Поведение курсора выборки (Ложное срабатывание в отладчике)

Рассмотрим стандартный цикл работы с результатом запроса в 1С. Когда мы пишем конструкцию:


Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    // Обработка данных
КонецЕсли;

Давайте разберем по шагам, что происходит на физическом уровне в оперативной памяти:

  1. Метод Выбрать() возвращает объект типа ВыборкаИзРезультатаЗапроса.
  2. В этот момент внутренний указатель (курсор) выборки спозиционирован в положении перед первой записью (условно на позиции -1). Это означает, что текущей строки данных еще нет.
  3. Если в этот момент (на строке с методом Выбрать()) попытаться посмотреть в отладчике свойства выборки (например, Выборка.Руководитель или Выборка.Должность), платформа выдаст «Ошибка чтения значения». И это абсолютно нормальное, штатное поведение системы! Платформа физически не может прочитать поля записи, на которую курсор еще не указывает.
  4. Только после того, как управление передается на строку Выборка.Следующий(), курсор сдвигается на первую строку (индекс 0). Если метод возвращает значение Истина, поля выборки мгновенно становятся доступными для чтения как в коде, так и в табло отладчика.
  5. Если же запрос вернул пустой результат, вызов метода Следующий() вернет значение Ложь. Курсор останется в неопределенном положении, а попытка прочитать свойства объекта в отладчике по-прежнему будет сопровождаться ошибкой чтения.

Вывод: Никогда не пытайтесь анализировать поля переменной выборки в отладчике до того, как код успешно выполнил метод Следующий() и зашел внутрь цикла или условия Если.


Как правильно проверять результаты запроса в отладчике?

Если вы стоите на точке останова перед обходом выборки и хотите убедиться, что запрос вообще что-то вернул, используйте проверенные временем инструменты платформы:


Причина №2. Неправильная фильтрация в виртуальных таблицах (СрезПоследних)

Часто «Ошибка чтения значения» в коде является следствием того, что запрос действительно возвращает пустой результат. И виной тому — грубейшая ошибка при работе с виртуальными таблицами периодических регистров сведений (таких как СведенияОбОтветственныхЛицах или ФИОФизическихЛиц).

Рассмотрим ошибочный вариант запроса, который часто пишут начинающие разработчики:


ВЫБРАТЬ
    ОтветственныеЛица.Руководитель КАК Руководитель
ИЗ
    РегистрСведений.СведенияОбОтветственныхЛицах.СрезПоследних КАК ОтветственныеЛица
ГДЕ
    ОтветственныеЛица.Период <= &Период
    И ОтветственныеЛица.Организация = &Организация

В чем заключается критическая ошибка?

Когда мы накладываем условие по дате (Период <= &Период) или по измерению в секции ГДЕ, СУБД выполняет запрос следующим образом:

  1. Сначала платформа рассчитывает виртуальную таблицу СрезПоследних на текущую дату (на самый конец времени).
  2. Из этого среза (в котором на каждую организацию приходится ровно по одной последней записи) СУБД начинает отсекать строки по условию в ГДЕ.
  3. Если актуальный руководитель организации изменился вчера, а мы ищем руководителя на прошлый месяц, условие Период <= &Период просто отбросит актуальную запись. На выходе мы получим пустой результат, хотя в истории регистра за прошлый месяц данные были!

Правильное решение: Условия по дате сплита (периоду среза) и разрезам аналитики (измерениям) должны передаваться строго в параметры виртуальной таблицы.

Перепишем запрос правильно:


ВЫБРАТЬ
    ОтветственныеЛица.Руководитель КАК Руководитель
ИЗ
    РегистрСведений.SведенияОбОтветственныхЛицах.СрезПоследних(&Период, Организация = &Организация) КАК ОтветственныеЛица

В этом случае платформа передаст параметры непосредственно в движок расчета виртуальной таблицы. СУБД сначала ограничит данные нужной датой и организацией, а затем выберет последние актуальные записи на указанный момент времени. Запрос гарантированно вернет корректные данные, и выборка не будет пустой.


Профессиональное проектирование возвращаемых значений

Чтобы избежать ошибок типов (когда функция вместо ожидаемой структуры возвращает Неопределено, что приводит к падению системы при попытке обращения к свойствам), применим безопасный паттерн проектирования функций.

Вместо жесткого конструирования структуры «на лету» внутри условия Следующий(), создадим структуру заранее и заполним её свойства с помощью системного метода ЗаполнитьЗначенияСвойств:


Функция ПолучитьРуководителя(Организация, Дата) Экспорт

    // Инициализируем структуру с дефолтными значениями (пустыми ссылками)
    РезультатФункции = Новый Структура("Руководитель, Должность, ОснованиеПодписи", 
        Справочники.ФизическиеЛица.ПустаяСсылка(),
        Справочники.Должности.ПустаяСсылка(),
        Неопределено
    );

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

    Запрос.УстановитьПараметр("Организация", Организация);
    // Передаем границу периода для корректного захвата документов, проведенных в ту же секунду
    Запрос.УстановитьПараметр("Период", Новый Граница(Дата, ВидГраницы.Включая));

    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        // Автоматически сопоставляем имена полей выборки и ключи структуры
        ЗаполнитьЗначенияСвойств(РезультатФункции, Выборка);
    КонецЕсли;

    Возврат РезультатФункции;

КонецФункции

Преимущества данного подхода:

← На главную