Как проверить, равно ли значение перечисления нужному: встроенный язык и запросы 1С

· 9 мин

Содержание

Значение перечисления в 1С сравнивают со ссылкой на конкретное значение, а не со строкой: в коде — Если Документ.ВидОперации = Перечисления.ВидыОпераций.Поступление Тогда, в запросе — ГДЕ Т.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Поступление). Для перечислений работают только операторы = и <>, а «больше/меньше» по порядку значений получают через метод Индекс() менеджера перечисления.

Всё, что ниже, относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: одинаково пишется в Бухгалтерии, ЗУП, УТ и в собственной разработке. Имена перечислений в примерах условные — подставьте свои из дерева метаданных.

Как устроено значение перечисления и чем его сравнивать

Перечисление в 1С — это объект метаданных с фиксированным списком значений, которые задаются в Конфигураторе. В базе каждое значение хранится как ссылка типа ПеречислениеСсылка.<Имя>. Поэтому сравнение перечислений — это сравнение двух ссылок.

У каждого значения есть три «лица», и путать их нельзя:

  • 🔑 Имя — идентификатор в метаданных, например Поступление. Не меняется при переводе интерфейса.
  • 🏷️ Синоним — то, что видит пользователь: «Поступление товаров». Это строка, она зависит от языка.
  • 🔗 Ссылка — само значение в базе. Именно её хранит реквизит документа.

Сравнивать нужно ссылки. Ссылку на конкретное значение получают через глобальный менеджер Перечисления:

Вид = Перечисления.ВидыОпераций.Поступление;

Операторы, которые доступны для перечислений:

ОператорЧто делаетПример
=значения совпадаютВид = Перечисления.ВидыОпераций.Возврат
<>значения разныеВид <> Перечисления.ВидыОпераций.Возврат
НЕ, И, ИЛИобъединяют условияВид = А ИЛИ Вид = Б
<, >, <=, >=для ссылок в коде не работаютплатформа выдаст ошибку сравнения

Сравнение значений перечисления во встроенном языке

Базовая форма условия — обычный Если:

Если Объект.ВидОперации = Перечисления.ВидыОпераций.Поступление Тогда
    ЗаполнитьПоступление();
ИначеЕсли Объект.ВидОперации = Перечисления.ВидыОпераций.Возврат Тогда
    ЗаполнитьВозврат();
Иначе
    ВызватьИсключение "Не задан вид операции";
КонецЕсли;

Для короткого выбора одного из двух значений подходит тернарный оператор ?():

Знак = ?(Объект.ВидОперации = Перечисления.ВидыОпераций.Возврат, -1, 1);

Сравнение на клиенте

В модуле управляемой формы, в процедуре с директивой &НаКлиенте, принято обращаться к значению через функцию ПредопределенноеЗначение(). Она одинаково работает и для перечислений, и для предопределённых элементов справочников, поэтому код получается однообразным:

&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
    Если Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОпераций.Возврат") Тогда
        Элементы.Основание.Видимость = Истина;
    Иначе
        Элементы.Основание.Видимость = Ложь;
    КонецЕсли;
КонецПроцедуры

Строка в ПредопределенноеЗначение() пишется через единственное число: Перечисление., а не Перечисления.. Это тот же синтаксис, что в запросе.

Если имя значения указано с опечаткой, ПредопределенноеЗначение() выдаст ошибку во время выполнения, а не при синтаксическом контроле. Проверяйте такие ветки руками хотя бы один раз.

Как сравнить значение реквизита ссылки без лишнего чтения

Конструкция ДокументСсылка.ВидОперации в серверном коде читает из базы весь объект документа, чтобы достать одно поле. В цикле по сотне ссылок это сотня полных чтений. Нужный реквизит лучше получить одним запросом или функцией БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта(), если она есть в вашей конфигурации.

Перечисления в условиях запроса 1С

В тексте запроса менеджера Перечисления нет. Конкретное значение задают функцией ЗНАЧЕНИЕ() или передают параметром.

Способ 1. Функция ЗНАЧЕНИЕ()

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Док.Ссылка,
|    Док.СуммаДокумента
|ИЗ
|    Документ.Операция КАК Док
|ГДЕ
|    Док.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Поступление)";

Внутри ЗНАЧЕНИЕ() — тоже единственное число: Перечисление.ВидыОпераций.Поступление. Имя значения проверяется при разборе текста запроса, поэтому опечатка всплывёт сразу при выполнении.

Способ 2. Параметр запроса

Запрос.Текст =
"ВЫБРАТЬ
|    Док.Ссылка
|ИЗ
|    Документ.Операция КАК Док
|ГДЕ
|    Док.ВидОперации = &ВидОперации";
Запрос.УстановитьПараметр("ВидОперации", Перечисления.ВидыОпераций.Поступление);

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

СпособКогда братьМинус
ЗНАЧЕНИЕ()значение фиксировано логикойменять придётся в тексте запроса
&Параметрзначение приходит снаружилегко забыть УстановитьПараметр

Имя в УстановитьПараметр() должно совпадать с именем в тексте без амперсанда: &ВидОперации → "ВидОперации". Если в параметр по ошибке передали синоним-строку, запрос выполнится, но не найдёт ни одной строки.

Пустое значение перечисления в запросе

Незаполненный реквизит типа перечисления хранит пустую ссылку. В запросе её пишут так:

"ГДЕ Док.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.ПустаяСсылка)"

Если реквизит составного типа, вместо пустой ссылки там может оказаться NULL или значение другого типа. Тогда проверяют тип:

"ГДЕ Док.Значение ССЫЛКА Перечисление.ВидыОпераций"

Строки, где значение отсутствует после левого соединения, ловят через ЕСТЬ NULL, а не через сравнение с пустой ссылкой.

Конструкция ВЫБОР для значений перечисления

ВЫБОР КОГДА нужен, когда значение перечисления надо превратить в число, строку или признак прямо в выборке. Типичный пример — знак суммы для приходов и расходов:

"ВЫБРАТЬ
|    Док.Ссылка,
|    ВЫБОР
|        КОГДА Док.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Возврат)
|            ТОГДА -Док.СуммаДокумента
|        ИНАЧЕ Док.СуммаДокумента
|    КОНЕЦ КАК СуммаСоЗнаком
|ИЗ
|    Документ.Операция КАК Док"

Несколько веток записывают подряд, первая совпавшая выигрывает:

"ВЫБОР
|    КОГДА Т.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Новый) ТОГДА 1
|    КОГДА Т.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.ВРаботе) ТОГДА 2
|    КОГДА Т.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Закрыт) ТОГДА 3
|    ИНАЧЕ 0
|КОНЕЦ КАК ПорядокСтатуса"

Ветку ИНАЧЕ не пропускайте. Без неё для пустого значения выражение вернёт NULL, и итог по колонке может разойтись с ожидаемым.

Аналог ВЫБОР в коде

Оператора вроде switch во встроенном языке нет. Длинную цепочку ИначеЕсли можно заменить соответствием «значение → результат»:

Коэффициенты = Новый Соответствие;
Коэффициенты.Вставить(Перечисления.ВидыОпераций.Поступление, 1);
Коэффициенты.Вставить(Перечисления.ВидыОпераций.Возврат, -1);

Коэф = Коэффициенты.Получить(Объект.ВидОперации);
Если Коэф = Неопределено Тогда
    Коэф = 0;
КонецЕсли;

Ссылки перечислений корректно работают ключами соответствия: поиск идёт по значению ссылки.

Проверка, входит ли значение в список значений перечисления

Когда подходит любое из нескольких значений, цепочка ИЛИ быстро становится нечитаемой. И в запросе, и в коде есть способ короче.

В запросе: оператор В

Список прямо в тексте:

"ГДЕ Док.ВидОперации В (
|    ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Поступление),
|    ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Возврат))"

Список параметром — массив или список значений:

Виды = Новый Массив;
Виды.Добавить(Перечисления.ВидыОпераций.Поступление);
Виды.Добавить(Перечисления.ВидыОпераций.Возврат);

Запрос.Текст = "... ГДЕ Док.ВидОперации В (&Виды)";
Запрос.УстановитьПараметр("Виды", Виды);

Обратная проверка — НЕ Док.ВидОперации В (&Виды).

В коде: Найти() у массива

Если Виды.Найти(Объект.ВидОперации) <> Неопределено Тогда
    // значение входит в список
КонецЕсли;

Метод Найти() возвращает индекс элемента или Неопределено. Сравнивать результат с нулём нельзя: индекс 0 — это первый элемент, найденный. У списка значений аналог — НайтиПоЗначению(), он тоже возвращает Неопределено, если ничего нет.

Если список нужен часто, соберите его один раз в функции модуля менеджера и возвращайте ФиксированныйМассив — его нельзя случайно изменить в вызывающем коде.

Пустые значения и порядок значений перечисления

Как проверить, заполнено ли значение

Три равнозначных по смыслу способа для реквизита простого типа:

Если НЕ ЗначениеЗаполнено(Объект.ВидОперации) Тогда ...
Если Объект.ВидОперации.Пустая() Тогда ...
Если Объект.ВидОперации = Перечисления.ВидыОпераций.ПустаяСсылка() Тогда ...

Обратите внимание: в коде ПустаяСсылка() — это метод со скобками, а в запросе ПустаяСсылка пишется без них.

Для реквизита составного типа ЗначениеЗаполнено() безопаснее: там может лежать Неопределено, и вызов .Пустая() на нём упадёт.

«Больше» и «меньше» по порядку значений

Порядок значений задаётся в Конфигураторе, и иногда логика от него зависит: статус «Закрыт» идёт после «В работе». Напрямую написать Статус > Перечисления.СтатусыЗаказов.ВРаботе нельзя, но можно сравнить индексы:

ИндексТекущего = Перечисления.СтатусыЗаказов.Индекс(Заказ.Статус);
ИндексГраницы  = Перечисления.СтатусыЗаказов.Индекс(Перечисления.СтатусыЗаказов.ВРаботе);

Если ИндексТекущего > ИндексГраницы Тогда
    // статус дальше, чем «В работе»
КонецЕсли;

Индексы начинаются с нуля. Обратный метод Получить(Индекс) возвращает значение по номеру.

Логика на индексах ломается, если кто-то переставит значения в Конфигураторе. Такой код оставляйте с комментарием, а надёжнее — явное соответствие «статус → номер этапа».

Как получить имя значения

Для логов и обмена иногда нужно не синоним, а имя из метаданных. Его возвращает XMLСтрока(Значение): для Перечисления.ВидыОпераций.Возврат получится строка Возврат. Через метаданные то же самое:

Индекс = Перечисления.ВидыОпераций.Индекс(Значение);
ИмяЗначения = Метаданные.Перечисления.ВидыОпераций.ЗначенияПеречисления[Индекс].Имя;

Для пустой ссылки Индекс() использовать нельзя — сначала проверьте ЗначениеЗаполнено().

Ошибки при сравнении перечислений и как их исправить

Диагностика: выберите симптом

Отдельно о составных типах. Если реквизит может хранить и перечисление, и, например, справочник, сравнение с перечислением формально корректно, но перед .Пустая() или Индекс() нужно убедиться в типе:

Если ТипЗнч(Значение) = Тип("ПеречислениеСсылка.ВидыОпераций") Тогда
    Индекс = Перечисления.ВидыОпераций.Индекс(Значение);
КонецЕсли;

Ещё одна неочевидная ошибка — проверка через представление в запросе: ГДЕ ПРЕДСТАВЛЕНИЕ(Док.ВидОперации) = "Возврат". Такое условие и медленнее, и ломается при переименовании синонима.

Быстродействие и правила хорошего кода

Сравнение двух ссылок перечисления дешёвое — это сравнение идентификаторов. Тормозит обычно не оно, а то, что вокруг.

  • ⚡ Фильтруйте в запросе, а не в цикле. Выбрать все документы и в коде отбросить ненужные виды операций — лишний трафик с сервера СУБД. Условие ГДЕ ... В (&Виды) отработает на стороне базы.
  • 🧭 Не разыменовывайте ссылку в цикле. Ссылка.ВидОперации читает объект целиком. Получите поле запросом сразу для всех ссылок.
  • 🧱 Не переводите в строку. ВЫРАЗИТЬ, ПРЕДСТАВЛЕНИЕ и Строка() в условиях лишают сравнение смысла и мешают использовать индексы.
  • 📦 Держите списки значений в одном месте. Функция модуля менеджера «Виды операций, уменьшающие остаток» лучше пяти копий одного ИЛИ в разных модулях. Новое значение перечисления добавят — поправите одну функцию.
  • 🛑 Ставьте ветку на неизвестное значение. В Если ... ИначеЕсли последний Иначе с ВызватьИсключение сразу покажет, что в перечисление добавили значение, а код о нём не знает.
  • 🔍 Ищите все места сравнения перед изменением перечисления. Глобальный поиск по модулям в Конфигураторе (Ctrl+Shift+F) по строке ВидыОпераций.Возврат найдёт и код, и тексты запросов. Учтите, что строки, собранные динамически, так не найти.

Если значение используется в десятках модулей, не удаляйте его, а пометьте как устаревшее, например переименуйте с префиксом Удалить. Так старые документы с этим значением продолжат открываться.

Частые вопросы

Можно ли сравнить два реквизита типа перечисления между собой?
Да, обычным =: Если Строка1.Вид = Строка2.Вид Тогда. Если оба пустые, результат будет Истина — учитывайте это в логике.
Можно ли сравнить значения разных перечислений?
Сравнение не упадёт, но всегда даст Ложь: это ссылки разных типов. Если два перечисления описывают одно и то же, сопоставьте их через соответствие.
Как отсортировать выборку по перечислению в порядке Конфигуратора?
В запросе используйте УПОРЯДОЧИТЬ ПО Т.Статус: перечисления сортируются по порядку значений в метаданных, а не по алфавиту синонимов.
Почему в коде Перечисления, а в запросе Перечисление?
В коде вы обращаетесь к глобальному свойству-коллекции менеджеров Перечисления. В запросе и в ПредопределенноеЗначение() указывается имя таблицы метаданных, которое пишется в единственном числе.
Как передать значение перечисления в СКД?
Через параметр компоновки или в тексте запроса набора данных той же функцией ЗНАЧЕНИЕ(). В отборе пользователь выбирает значение из списка, сравнивать строки не нужно.