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