При работе с запросами в 1С начинающие разработчики часто сталкиваются с ситуацией, когда запрос не возвращает ожидаемые данные или работает некорректно, если в условии ГДЕ используется поле с составным типом данных. Ситуация осложняется тем, что система не выдает ошибку, а просто возвращает пустой результат. В этой статье мы подробно разберем, как правильно работать с такими полями, оптимизировать код и на что обратить внимание при передаче параметров.
Рассмотрим ситуацию: у нас есть документ СдельныйНаряд, в котором поле Исполнитель имеет составной тип (например, Справочник.ФизическиеЛица и Справочник.Бригады). Программист пытается выполнить следующий запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СдельныйНаряд.Ссылка КАК Ссылка,
| СдельныйНаряд.Номер КАК Номер,
| СдельныйНаряд.Исполнитель КАК Исполнитель
|ИЗ
| Документ.СдельныйНаряд КАК СдельныйНаряд
|ГДЕ
| СдельныйНаряд.Дата МЕЖДУ &НачДата И &КонДата
| И СдельныйНаряд.Проведен = ИСТИНА
| И СдельныйНаряд.Исполнитель = &Исполнитель";
Запрос.УстановитьПараметр("Исполнитель", Объект.Исполнитель);
Запрос.УстановитьПараметр("НачДата", Объект.Начало);
Запрос.УстановитьПараметр("КонДата", Объект.Конец);
РезультатЗапроса = Запрос.Выполнить();
Несмотря на то что код кажется верным, он может возвращать пустой результат. Давайте разберем по шагам, почему это происходит и как это исправить.
Одной из главных причин "молчаливого" отказа запроса или его медленной работы является природа составных типов в базе данных SQL. Когда поле составное, платформа 1С хранит его в нескольких колонках (идентификатор типа и сама ссылка). При обычном сравнении Исполнитель = &Исполнитель СУБД приходится выполнять сложные соединения (LEFT JOIN) со всеми таблицами, входящими в составной тип.
Для оптимизации и корректной работы мы рекомендуем использовать оператор ВЫРАЗИТЬ. Он явно указывает системе, какой тип данных мы ожидаем в этом поле. Проанализируем, как изменится условие:
Вариант с явным приведением типа:
|ГДЕ
| ВЫРАЗИТЬ(СдельныйНаряд.Исполнитель КАК Справочник.ФизическиеЛица) = &Исполнитель
Преимущества этого подхода:
Если в параметр &Исполнитель может быть передано как физическое лицо, так и бригада, использование только ВЫРАЗИТЬ может привести к тому, что значения другого типа будут отсечены (превращены в NULL). В таких случаях правильно будет сначала проверить тип данных:
|ГДЕ
| (СдельныйНаряд.Исполнитель ССЫЛКА Справочник.ФизическиеЛица И СдельныйНаряд.Исполнитель = &Исполнитель)
| ИЛИ (СдельныйНаряд.Исполнитель ССЫЛКА Справочник.Бригады И СдельныйНаряд.Исполнитель = &Исполнитель)
Этот подход гарантирует, что запрос отработает корректно для любого из типов, входящих в составной реквизит.
Часто проблема "неработающего" запроса кроется не в составном типе, а в параметре &КонДата. Выясним причину: если пользователь выбирает дату в поле Объект.Конец (например, 31.10.2023), то по умолчанию время устанавливается в 00:00:00. Если документ был создан 31.10.2023 в 12:00:00, он не попадет в интервал МЕЖДУ, так как он позже начала дня.
Как правильно передавать дату:
// Обязательно используйте КонецДня для верхней границы периода
Запрос.УстановитьПараметр("КонДата", КонецДня(Объект.Конец));
Либо используйте объект Граница, чтобы включить документы, записанные в ту же секунду:
Запрос.УстановитьПараметр("КонДата", Новый Граница(КонецДня(Объект.Конец), ВидГраницы.Включая));
Проанализируем еще одну тонкость составных типов. Поле может содержать значение Неопределено (если значение вообще не выбрано) или ПустаяСсылка конкретного типа (например, пустая ссылка справочника ФизическиеЛица).
Если в параметре &Исполнитель передается Неопределено, а в базе данных записана пустая ссылка конкретного типа, то условие СдельныйНаряд.Исполнитель = &Исполнитель вернет ЛОЖЬ. Перед выполнением запроса убедитесь, что в параметр передается именно то значение, которое ожидается в базе. Для проверки заполнения в коде можно использовать ЗначениеЗаполнено(Объект.Исполнитель).
Для того чтобы фильтрация по составному полю в больших таблицах не вызывала "тормозов", необходимо убедиться, что для этого реквизита в метаданных установлено свойство Индексировать или Индексировать с дополнительным упорядочиванием.
Однако помните, что индексация составного поля всегда менее эффективна, чем индексация простого ссылочного поля. Поэтому везде, где логика приложения позволяет, старайтесь использовать оператор ВЫРАЗИТЬ в тексте запроса. Это считается "золотым стандартом" разработки на платформе 1С:Предприятие 8.
С учетом всех разобранных нюансов, наиболее надежный и быстрый вариант кода будет выглядеть так:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СдельныйНаряд.Ссылка КАК Ссылка,
| СдельныйНаряд.Номер КАК Номер,
| СдельныйНаряд.Исполнитель КАК Исполнитель
|ИЗ
| Документ.СдельныйНаряд КАК СдельныйНаряд
|ГДЕ
| СдельныйНаряд.Дата МЕЖДУ &НачДата И &КонДата
| И СдельныйНаряд.Проведен = ИСТИНА
| И СдельныйНаряд.Исполнитель = &Исполнитель";
// Правильная установка параметров
Запрос.УстановитьПараметр("Исполнитель", Объект.Исполнитель);
Запрос.УстановитьПараметр("НачДата", НачалоДня(Объект.Начало));
Запрос.УстановитьПараметр("КонДата", КонецДня(Объект.Конец));
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Сообщить("Данные не найдены. Проверьте заполнение исполнителя и период!");
КонецЕсли;
Разбирая подобные ситуации, мы видим, что мелочи вроде времени в дате или особенностей хранения составных типов в SQL могут существенно влиять на результат работы программы. Внимательное отношение к типам данных и границам периодов — залог стабильной работы ваших разработок.