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

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

В процессе разработки и отладки программного кода в среде 1С:Предприятие каждый разработчик сталкивается с ситуациями, когда написанный запрос отказывается работать, а в окне отладки (Табло или Вычисление выражения) выводится неинформативное сообщение: «ошибка чтения значения». Эта проблема может быть вызвана целым комплексом причин — от банальных опечаток в именах переменных до фундаментальных архитектурных ошибок проектирования запросов, которые приводят к несовместимости типов на уровне СУБД.

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

Разбор ситуации: Опечатка в имени переменной выборки

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


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

Присмотримся внимательнее к именам переменных. Результат обхода выборки помещается в переменную Рез2. Однако в операторе условия Если разработчик ошибочно вызывает метод Следующий() у переменной Рез, которая в данном контексте либо вообще не определена, либо хранит совершенно другие данные.

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

Для исправления этой досадной опечатки необходимо строго следить за именованием переменных выборки:


ВыборкаДетальныеЗаписи = Запрос.Выполнить().Выбрать();
Если ВыборкаДетальныеЗаписи.Следующий() Тогда
    КоллекцияДанных.Вставить("ТрудДогНом", ВыборкаДетальныеЗаписи.Номер);
    КоллекцияДанных.Вставить("ТрудДогДата", ВыборкаДетальныеЗаписи.Дата);
КонецЕсли;

Пропущенный параметр запроса

Следующая критическая ошибка в представленном коде — это использование в тексте запроса параметра, который не был предварительно установлен во встроенном языке. Обратим внимание на строчку в тексте запроса:

И ПриемНаРаботу.ДатаПриема <= &ДатаДокумента

При этом в коде инициализации параметров мы видим только установку параметров Организация и Сотрудник:


Запрос.УстановитьПараметр("Организация", ?(ОрганизацияСотрудника.Пустая(), 0, ОрганизацияСотрудника));
Запрос.УстановитьПараметр("Сотрудник", Ссылка2);

Параметр &ДатаДокумента остался неопределенным. При попытке выполнить такой запрос платформа 1С сгенерирует исключение выполнения запроса, а при попытке проанализировать переменные выборки в Табло отладчика мы снова увидим ошибку чтения значения. Перед вызовом метода Выполнить() обязательно нужно передать все параметры, объявленные в тексте запроса с символом амперсанда &:


Запрос.УстановитьПараметр("ДатаДокумента", ТекущаяДата()); // Или ДатаДокумента, в зависимости от контекста

Глубокий анализ: Почему Табло отладчика выдает ошибку при чтении выборки?

Давайте разберем внутреннюю механику работы платформы 1С:Предприятие, чтобы понять, почему при отладке запросов в Табло так часто возникает эта ошибка.

Когда мы пишем выражение Запрос.Выполнить().Выбрать(), система возвращает нам объект типа ВыборкаИзРезультатаЗапроса. Этот объект представляет собой однонаправленный последовательный курсор (итератор) для обхода строк таблицы данных. Особенности его работы заключаются в следующем:

  1. Начальное состояние итератора: Сразу после создания выборки курсор позиционирован в специальном системном состоянии «перед первой записью» (индекс -1). В этой позиции в выборке еще нет текущей строки, и ни одно из полей выборки (например, Номер или Дата) физически не заполнено данными.
  2. Поведение отладчика: Если вы добавите в Табло выражение Рез2.Номер в тот момент, когда выполнение кода остановлено на строке сразу после метода Выбрать(), система попытается прочитать значение поля текущей строки. Но так как текущей строки еще нет (метод Следующий() еще не вызывался), платформа вернет ошибку чтения значения.
  3. Правильный способ просмотра данных в Табло: Если вам необходимо оперативно посмотреть, какие именно данные вернул запрос во время сеанса отладки, не проходя циклы обхода, используйте метод выгрузки в таблицу значений. Напишите в окне Табло следующее выражение: Запрос.Выполнить().Выгрузить(). Платформа сформирует полноценную ТаблицуЗначений в оперативной памяти и позволит вам просмотреть ее структуру и строки в удобном визуальном окне отладчика.

Архитектурная проблема: Несовместимость типов в СУБД при сравнении с нулем

Теперь проанализируем самую опасную скрытую ошибку в коде автора темы, связанную с передачей параметров и условной фильтрацией. Рассмотрим конструкцию передачи параметра:


Запрос.УстановитьПараметр("Организация", ?(ОрганизацияСотрудника.Пустая(), 0, ОрганизацияСотрудника));

И соответствующий фрагмент в тексте запроса:


ГДЕ
    ВЫБОР
        КОГДА &Организация = 0
            ТОГДА ИСТИНА
        ИНАЧЕ ПриемНаРаботу.Организация = &Организация
    КОНЕЦ

Данный подход является грубым нарушением стандартов разработки на платформе 1С и приводит к серьезным проблемам производительности на уровне СУБД (MS SQL Server, PostgreSQL, IBM DB2). Рассмотрим, почему так происходит:

  1. Разнородные типы данных в одном параметре: Переменная ОрганизацияСотрудника имеет ссылочный тип (например, СправочникСсылка.Организации). Если ссылка пустая, разработчик передает в параметр число 0. Таким образом, параметр &Организация становится полиморфным: он может содержать либо уникальный идентификатор (UUID) ссылки, либо числовое значение.
  2. Проблема неявного приведения типов (Implicit Conversion): В СУБД поле Организация в таблице документов хранится в виде бинарных данных (16 байт) или уникального идентификатора. Когда SQL-сервер выполняет сравнение ПриемНаРаботу.Организация = &Организация, а в параметр передан 0 (число), СУБД вынуждена приводить типы данных к одному общему знаменателю для выполнения операции сравнения. Это приводит к тому, что СУБД не может использовать индекс по полю Организация.
  3. Деградация производительности (Index Scan вместо Index Seek): Из-за невозможности использовать существующие индексы SQL-сервер выполняет полное сканирование всей таблицы документов (Table Scan или Index Scan) вместо быстрого точечного поиска (Index Seek). На реальных базах данных с миллионами документов такой запрос начнет вызывать критические зависания системы и таймауты блокировок.
  4. Ошибки выполнения: В некоторых конфигурациях СУБД и при определенных настройках строгого соответствия типов подобное сравнение ссылки с числовым литералом напрямую вызовет аварийное завершение запроса с ошибкой несовместимости типов данных.

Как правильно реализовать условный фильтр по организации

Чтобы избежать неявного приведения типов и падения производительности, мы должны передавать в параметр только типизированные данные. Если организация не заполнена, мы должны работать с пустой ссылкой того же типа, а не с числом 0.

Рассмотрим правильный и лаконичный вариант написания кода на встроенном языке:


// Передаем ссылку «как есть» — пустую или заполненную. Никаких преобразований в число делать нельзя!
Запрос.УстановитьПараметр("Организация", ОрганизацияСотрудника);
Запрос.УстановитьПараметр("Сотрудник", Ссылка2);
Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);

Теперь оптимизируем текст самого запроса. Вместо тяжелой конструкции ВЫБОР КОГДА ... КОНЕЦ, которая также усложняет построение оптимального плана запроса для оптимизатора СУБД, применим классическое логическое условие ИЛИ с проверкой на пустую ссылку через предопределенное ключевое слово ЗНАЧЕНИЕ():


ВЫБРАТЬ ПЕРВЫЕ 1
    ПриемНаРаботу.Номер КАК Номер,
    ПриемНаРаботу.Дата КАК Дата
ИЗ
    Документ.ПриемНаРаботу КАК ПриемНаРаботу
ГДЕ
    (&Организация = ЗНАЧЕНИЕ(Справочник.Организации.ПустаяСсылка)
        ИЛИ ПриемНаРаботу.Организация = &Организация)
    И ПриемНаРаботу.Сотрудник = &Сотрудник
    И ПриемНаРаботу.ДатаПриема <= &ДатаДокумента
    И ПриемНаРаботу.Проведен

УПОРЯДОЧИТЬ ПО
    ПриемНаРаботу.Дата УБЫВ

Давайте проанализируем, почему этот вариант запроса работает во много раз быстрее и стабильнее:

  1. Строгая типизация параметров: В параметр &Организация всегда передается либо заполненная ссылка на справочник организаций, либо пустая ссылка этого же справочника. На уровне СУБД типы данных всегда совпадают, неявного приведения типов не происходит.
  2. Эффективное использование индексов: Оптимизатор СУБД может легко построить план запроса, задействующий индекс по полям Организация и Сотрудник, обеспечивая моментальный поиск нужной записи.
  3. Отсутствие конструкции ВЫБОР в секции ГДЕ: Использование логического оператора ИЛИ вместо условных операторов выбора позволяет СУБД оптимально кэшировать планы выполнения запроса.

Итоговый правильный шаблон написания кода

Давайте соберем все наши исправления в единый красивый, производительный и надежный шаблон кода, который никогда не вызовет ошибок чтения значений при отладке:


// 1. Инициализируем объект запроса
Запрос = Новый Запрос;

// 2. Устанавливаем строго типизированные параметры
Запрос.УстановитьПараметр("Организация",  ОрганизацияСотрудника);
Запрос.УстановитьПараметр("Сотрудник",    Ссылка2);
Запрос.УстановитьПараметр("ДатаДокумента", ТекДата); // Передаем корректную дату

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

// 4. Выполняем запрос и получаем выборку
РезультатЗапроса = Запрос.Выполнить();

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

Соблюдение этих простых архитектурных правил позволит вам полностью исключить возникновение трудночитаемых ошибок при отладке кода, а ваши запросы будут выполняться максимально быстро даже на сильно нагруженных информационных базах 1С.

← На главную