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