Как правильно передавать перечисления в параметры запроса 1С и избегать типичных ошибок?

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

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

Разбор ситуации: почему фильтр по перечислению возвращает пустой результат?

Рассмотрим типичный сценарий. У нас есть запрос, который собирает данные о стоимости услуг, температурных коэффициентах и тепловой нагрузке. В запросе присутствует условие фильтрации по году, где год представлен типом ПеречислениеСсылка.Годы. Автор столкнулся с тем, что при исключении условия ГДЕ РасчетыТемпературы.Год = &Год данные выбираются, а с ним — нет.

Проанализируем фрагмент исходного кода:


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

Первая и самая очевидная причина проблемы кроется в способе передачи параметра в метод УстановитьПараметр. Если код выполняется в контексте управляемой формы, данные формы (например, реквизит Объект.Год) могут требовать явного обращения через структуру данных объекта.

Решение №1: Правильная передача параметра

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


// Если работа идет в модуле формы (Управляемое приложение)
Запрос.УстановитьПараметр("Год", Объект.РасчетыТемпературы.Год);

// Если значение получено из переменной, нужно убедиться, что в ней ссылка
Запрос.УстановитьПараметр("Год", ВыбранныйГод);

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

Решение №2: Использование конструкции ЗНАЧЕНИЕ в тексте запроса

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

Рассмотрим пример:

ГДЕ РасчетыТемпературы.Год = ЗНАЧЕНИЕ(Перечисление.Годы.ГОД2019)

Этот способ гарантирует, что система обратится к конкретной ссылке перечисления напрямую в базе данных, минуя клиент-серверную сериализацию параметров.

Критическая ошибка: Левое соединение и секция ГДЕ

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

Почему это происходит?
Когда вы используете ЛЕВОЕ СОЕДИНЕНИЕ, система обязана вернуть все строки из левой таблицы. Если в правой таблице (РасчетыТемпературы) нет данных, соответствующих условию связи ПО, поля правой таблицы заполняются значениями NULL. Однако, когда выполнение доходит до секции ГДЕ, условие РасчетыТемпературы.Год = &Год проверяет этот самый NULL. Сравнение NULL = &Год всегда дает Ложь. В итоге все строки, где не было соответствия в правой таблице, отсекаются, и вы теряете данные из основной таблицы.

Как исправить?
Условия фильтрации для присоединяемых (правых) таблиц необходимо переносить из секции ГДЕ в секцию ПО. Рассмотрим правильную структуру:


|ИЗ РегистрСведений.ПлательщикиПоПомещениям.СрезПоследних КАК ПлательщикиПоПомещениямСрезПоследних
|ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Помещения КАК Помещения ПО ...
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РасчетыТемпературы КАК РасчетыТемпературы
|ПО Помещения.ТипЗдания = РасчетыТемпературы.ТипЗдания
|И РасчетыТемпературы.Год = &Год  // Условие перенесено сюда!
|ГДЕ ПлательщикиПоПомещениямСрезПоследних.Абонент = &Контрагент

При такой структуре запрос вернет все помещения, даже если для них не заданы расчеты температуры на указанный год. В полях температуры просто будет NULL или 0 (если применить функцию ЕСТЬNULL), но сами записи не исчезнут из результата.

Почему использование перечисления для "Года" — это плохая практика?

В ходе обсуждения коллеги справедливо заметили, что использование типа Перечисление для хранения годов (ГОД2018, ГОД2019 и т.д.) является "антипаттерном". Рассмотрим причины, почему так делать не стоит, и проанализируем альтернативы:

  1. Необходимость внесения изменений в конфигурацию: Каждый новый год программисту придется заходить в конфигуратор, добавлять новое значение перечисления и обновлять информационную базу. Это усложняет поддержку.
  2. Отсутствие арифметики: С типом Число вы можете сделать Год + 1. С перечислением вам придется писать сложные конструкции ВЫБОР КОГДА, чтобы определить следующий или предыдущий год.
  3. Проблемы сортировки: Перечисления в запросах сортируются не по синониму (2018, 2019), а по внутреннему порядку (индексу), установленному в конфигураторе. Если кто-то случайно переместит значения в дереве метаданных, порядок годов в отчетах нарушится.

Рекомендация: Для хранения года используйте тип Число (длина 4, точность 0) или тип Дата (начало года). Это позволит использовать стандартные функции ГОД() в запросах и сделает систему гибкой без участия программиста 1 января каждого года.

Итоги и рекомендации

Подведем итог нашему разбору. Если ваш запрос с перечислением не работает:

  1. Проверьте, что в метод УстановитьПараметр передается именно Ссылка на перечисление. Используйте Объект.ИмяРеквизита в управляемых формах.
  2. Если вы используете левое соединение, всегда выносите фильтры по правой таблице в условие ПО.
  3. Используйте ЗНАЧЕНИЕ(Перечисление.Имя.Значение) для статических фильтров прямо в тексте запроса.
  4. Старайтесь избегать использования перечислений для динамических данных, таких как годы или месяцы, заменяя их числами или датами.

Применяя эти правила, вы обеспечите стабильную работу своих запросов и корректность получаемых данных даже в сложных аналитических отчетах.

← На главную