Конструкция ВЫБОР КОГДА ТОГДА в запросе 1С: как писать условия

· 9 мин

Содержание

В языке запросов 1С условие «если — то — иначе» записывается так: ВЫБОР КОГДА <условие> ТОГДА <значение> ИНАЧЕ <значение> КОНЕЦ. Это выражение, а не оператор: оно возвращает одно значение и стоит там, где допустимо поле, — в списке выбираемых полей, в ГДЕ, в УПОРЯДОЧИТЬ ПО, в условии соединения.

Слова «Когда» и «Тогда» здесь не отдельные команды, а части конструкции ВЫБОР. Без КОНЕЦ запрос не скомпилируется. Без ИНАЧЕ результат для непопавших строк будет NULL, и это источник половины ошибок. Ниже — по порядку: от синтаксиса до соединений и ловушек.

Синтаксис: из чего состоит выражение

Минимальная форма:

ВЫБРАТЬ
    Номенклатура.Наименование КАК Наименование,
    ВЫБОР
        КОГДА Номенклатура.Услуга
            ТОГДА "Услуга"
        ИНАЧЕ "Товар"
    КОНЕЦ КАК Вид
ИЗ
    Справочник.Номенклатура КАК Номенклатура

Разберём части:

  • ВЫБОР — открывает выражение.
  • КОГДА — после него идёт логическое условие: сравнение, И, ИЛИ, НЕ, ЕСТЬ NULL, В (...), ПОДОБНО.
  • ТОГДА — значение, которое вернётся, если условие истинно.
  • ИНАЧЕ — значение «по умолчанию». Необязательная часть.
  • КОНЕЦ — закрывает выражение. Обязателен.

Ветвей КОГДА ... ТОГДА может быть сколько угодно:

ВЫБОР
    КОГДА Продажи.Сумма >= 100000 ТОГДА "Крупная"
    КОГДА Продажи.Сумма >= 10000 ТОГДА "Средняя"
    ИНАЧЕ "Мелкая"
КОНЕЦ КАК Категория

Для результата выражения почти всегда стоит давать псевдоним через КАК. Без него поле получит служебное имя, и в обработке результата обращаться к нему неудобно.

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

Ветви проверяются сверху вниз. Первая сработавшая побеждает, остальные не смотрятся вообще. Отсюда правило: сначала узкие и строгие условия, потом широкие.

Посмотрите на пример с суммой выше. Продажа на 150 000 удовлетворяет и первому, и второму условию, но получит «Крупная», потому что первое стоит выше. Поменяйте ветви местами — и все продажи от 10 000 станут «Средними», а «Крупная» не получит ни одной строки.

Типичный сбой выглядит так:

Порядок ветвейПродажа 150 000Продажа 15 000Продажа 500
Сначала >= 100000, потом >= 10000КрупнаяСредняяМелкая
Сначала >= 10000, потом >= 100000СредняяСредняяМелкая

Если условия не пересекаются (например, проверка равенства разным кодам), порядок ни на что не влияет. Но привычка идти от частного к общему всё равно спасает при правках запроса через полгода.

Что вернётся, если ничего не подошло

  • есть ИНАЧЕ — значение из ИНАЧЕ;
  • ИНАЧЕ нет — NULL.

И второе значение вы не увидите в отладчике как «пусто»: NULL не равен ни нулю, ни пустой строке, ни пустой ссылке. Если дальше в запросе поле участвует в арифметике, результат тоже станет NULL.

Выбор значения в списке выбираемых полей

Это самое частое применение. Несколько рабочих шаблонов.

Подпись вместо кода или булева значения

ВЫБОР
    КОГДА Договоры.ПометкаУдаления ТОГДА "Помечен на удаление"
    КОГДА Договоры.ДатаОкончания < &ТекущаяДата ТОГДА "Просрочен"
    ИНАЧЕ "Действует"
КОНЕЦ КАК Состояние

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

Условная сумма внутри агрегатной функции

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

ВЫБРАТЬ
    Продажи.Контрагент КАК Контрагент,
    СУММА(ВЫБОР
        КОГДА Продажи.Период >= &НачалоМесяца
            ТОГДА Продажи.Сумма
        ИНАЧЕ 0
    КОНЕЦ) КАК СуммаЗаМесяц,
    СУММА(Продажи.Сумма) КАК СуммаВсего
ИЗ
    РегистрНакопления.Продажи КАК Продажи
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент

Здесь ИНАЧЕ 0 обязателен. Без него для старых периодов выражение вернёт NULL. СУММА пропускает NULL, так что итог сойдётся, но если у контрагента вообще не было строк за месяц, придёт NULL вместо нуля, а при следующей операции над этим полем получите новый NULL.

То же работает для КОЛИЧЕСТВО: подсчёт строк с условием делается как СУММА(ВЫБОР КОГДА <условие> ТОГДА 1 ИНАЧЕ 0 КОНЕЦ).

Приведение типов результата

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

ВЫРАЗИТЬ(
    ВЫБОР
        КОГДА Строки.Количество = 0 ТОГДА 0
        ИНАЧЕ Строки.Сумма / Строки.Количество
    КОНЕЦ КАК ЧИСЛО(15, 2)) КАК Цена

Условие защищает от деления на ноль и пустых строк, а ВЫРАЗИТЬ фиксирует точность результата.

Строковые литералы в ветвях лучше тоже приводить: ВЫРАЗИТЬ("Да" КАК Строка(3)). Иначе поле получает строку неограниченной длины, а с такими полями ограничены группировка и сравнение.

Условия соединения таблиц

ВЫБОР допустим после ПО в соединении. Нужен он, когда ключ связи зависит от данных строки: в одних строках связываем по одному полю, в других — по другому.

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

Работает, но с оговорками:

  1. Индексы. Если в условии соединения стоит вычисляемое выражение, СУБД часто не может использовать индекс по полю. На таблицах в сотни тысяч строк это заметно в скорости.
  2. Читаемость. Условие в ПО длиннее двух ветвей уже трудно читать.
  3. Тип ключа. Все ветви выражения должны возвращать сопоставимые типы. Ссылку на контрагента нельзя «склеить» со строкой.

Альтернатива, которая обычно быстрее: разнести на два запроса во временные таблицы или сделать два левых соединения и выбрать результат уже в списке полей:

ВЫБОР
    КОГДА Документы.Комиссия ТОГДА КонтрагентКомитент.Наименование
    ИНАЧЕ КонтрагентОсновной.Наименование
КОНЕЦ КАК Наименование

Соединений станет два, зато каждое идёт по простому равенству.

Отдельная ловушка: условие в ПО и условие в ГДЕ для левого соединения — не одно и то же. Условие в ГДЕ по полю правой таблицы фактически превращает левое соединение во внутреннее, потому что строки с NULL отсекаются.

Вложенные условия и сложная логика

ВЫБОР можно вкладывать в ТОГДА, в ИНАЧЕ и даже в условие КОГДА.

ВЫБОР
    КОГДА Заказы.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Закрыт)
        ТОГДА "Завершён"
    ИНАЧЕ
        ВЫБОР
            КОГДА Заказы.ДатаОтгрузки < &Сегодня ТОГДА "Просрочен"
            КОГДА Заказы.Оплачен ТОГДА "К отгрузке"
            ИНАЧЕ "Ждёт оплаты"
        КОНЕЦ
КОНЕЦ КАК Этап

Вложение удобно, когда внутренняя логика имеет смысл только для части строк. Но у него есть предел: три уровня уже плохо читаются.

Что делать, если логика разрастается:

  • Склеить условия через И / ИЛИ. Две ветви с одинаковым результатом лучше объединить: КОГДА А ИЛИ Б ТОГДА Х.
  • Вынести часть вычислений во временную таблицу. Первым запросом посчитайте промежуточный признак, вторым используйте его в ВЫБОР.
  • Использовать В (...) вместо цепочки равенств: КОГДА Заказы.Статус В (ЗНАЧЕНИЕ(...), ЗНАЧЕНИЕ(...)) ТОГДА ....

Скобки и приоритет И и ИЛИ

Внутри условия И связывает сильнее, чем ИЛИ. Выражение А ИЛИ Б И В читается как А ИЛИ (Б И В). Если вы имели в виду (А ИЛИ Б) И В, поставьте скобки явно. Это та самая ошибка, из-за которой «в отчёте лишние строки» при вроде бы правильном условии.

Когда ВЫБОР не нужен

Не каждое «если» требует ВЫБОР. Три случая, где есть вариант проще:

  • замена NULL на значение — ЕСТЬNULL(Поле, 0);
  • фильтр по списку значений — Поле В (...) в ГДЕ;
  • постоянное соответствие «код → подпись» на десяток строк и больше — отдельный справочник или регистр сведений , подключённый соединением.

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

Условие в ГДЕ, УПОРЯДОЧИТЬ ПО и итогах

Кроме списка полей, ВЫБОР используют в:

  • ГДЕ, когда фильтр зависит от параметра: ГДЕ ВЫБОР КОГДА &ВсеКонтрагенты ТОГДА ИСТИНА ИНАЧЕ Продажи.Контрагент = &Контрагент КОНЕЦ. Рабочий приём, но запрос с таким фильтром оптимизируется хуже. Если можно, соберите текст запроса в коде и добавьте условие только когда оно нужно.
  • УПОРЯДОЧИТЬ ПО, когда нужен свой порядок: сначала «Просрочен», потом «Действует», потом прочие. Порядок задают числом: ВЫБОР КОГДА ... ТОГДА 1 КОГДА ... ТОГДА 2 ИНАЧЕ 3 КОНЕЦ.
  • ИТОГИ — внутри агрегатных функций, как в примере с СУММА выше.

Группировка по вычисленному признаку

Группировать по выражению ВЫБОР напрямую нельзя: в СГРУППИРОВАТЬ ПО ставят поля, а не вычисления. Рабочий путь — два шага через вложенный запрос или временную таблицу.

ВЫБРАТЬ
    Продажи.Контрагент КАК Контрагент,
    ВЫБОР
        КОГДА Продажи.Сумма >= 100000 ТОГДА "Крупная"
        ИНАЧЕ "Обычная"
    КОНЕЦ КАК Категория
ПОМЕСТИТЬ ВТПродажи
ИЗ
    РегистрНакопления.Продажи КАК Продажи
;

ВЫБРАТЬ
    ВТПродажи.Категория КАК Категория,
    КОЛИЧЕСТВО(*) КАК Количество
ИЗ
    ВТПродажи КАК ВТПродажи
СГРУППИРОВАТЬ ПО
    ВТПродажи.Категория

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

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

Значения перечислений и предопределённые элементы в КОГДА сравнивают через ЗНАЧЕНИЕ():

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

Литералов вроде "Закрыт" вместо ЗНАЧЕНИЕ не пишите: строка не равна значению перечисления.

Параметры внутри ВЫБОР

Параметры (&Параметр) можно подставлять и в условие, и в результат. Типичный пример — переключатель режима отчёта:

ВЫБОР
    КОГДА &ПоКонтрагентам ТОГДА Продажи.Контрагент
    ИНАЧЕ Продажи.Организация
КОНЕЦ КАК Разрез

Ветви возвращают разные типы ссылок, поэтому поле будет составного типа. Для вывода в отчёт это нормально, а для дальнейших соединений приведите типы или разделите запрос.

NULL, пустые значения и другие ловушки

Сравнение с NULL через = не даёт истины. Проверять нужно так:

ВЫБОР
    КОГДА Остатки.Количество ЕСТЬ NULL ТОГДА 0
    ИНАЧЕ Остатки.Количество
КОНЕЦ

Для этой же цели есть функция ЕСТЬNULL(Остатки.Количество, 0), она короче. Но ВЫБОР нужен, когда ветвей больше двух или значения разные для разных причин.

Самые частые проблемы:

СимптомПричинаЧто сделать
Вместо нуля приходит NULLНет ИНАЧЕ или данные из левого соединенияДобавить ИНАЧЕ 0 или обернуть в ЕСТЬNULL
Сумма не сходится с эталономВетви пересекаются, сработала не таПереставить условия от узкого к широкому
«Несовместимые типы» при выполненииВетви возвращают разные типы, поле используется в сравненииПривести через ВЫРАЗИТЬ
Запрос не компилируетсяПропущен КОНЕЦ или ТОГДАПроверить парность ключевых слов
Лишние строки в отчётеИЛИ и И без скобокРасставить скобки явно
Ветвь по пустой ссылке не срабатываетСравнение идёт с NULL или со строкойСравнивать с ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка)

Проверка запроса по шагам

Если результат вызывает сомнения, сделайте так:

  1. Откройте консоль запросов и вставьте запрос целиком.
  2. Временно выведите в результат сами признаки, на которых строится КОГДА, рядом с итоговым значением.
  3. Отдельно посмотрите строки с NULL в этих признаках.
  4. Только потом правьте порядок ветвей.
Проверка выражения ВЫБОР перед запуском на боевой базе
Выполнено: 0 / 6

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

Можно ли в 1С написать ВЫБОР без ИНАЧЕ?
Да. Для строк, где ни одно условие не сработало, вернётся NULL. Если дальше поле суммируется или сравнивается, лучше задавать ИНАЧЕ явно.
Чем ВЫБОР в запросе отличается от Если в коде?
Если в коде — оператор, он управляет ходом программы. ВЫБОР в запросе — выражение, оно вычисляется для каждой строки и возвращает значение. Писать вызовы процедур или присваивания внутри него нельзя.
Сколько ветвей КОГДА можно указать?
Жёсткого лимита в языке запросов нет. На практике больше 8-10 ветвей уже повод пересмотреть структуру: вынести соответствие в справочник или регистр сведений и подключить его соединением.
Работает ли ВЫБОР внутри функции СУММА или КОЛИЧЕСТВО?
Работает. Так делают условные агрегаты: СУММА(ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ 0 КОНЕЦ). Это быстрее, чем несколько отдельных запросов по одной таблице.
Почему ВЫБОР в соединении тормозит?
Вычисляемое выражение в условии ПО мешает использовать индекс. Если таблицы большие, замените его двумя соединениями по простому равенству.