ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ: условные поля, агрегаты и NULL в запросах 1С 8.3

· 11 мин

Содержание

В языке запросов 1С условие записывается одной конструкцией: ВЫБОР КОГДА <условие> ТОГДА <значение> ИНАЧЕ <значение> КОНЕЦ. Это аналог CASE WHEN … THEN … ELSE … END из SQL. Ветки проверяются сверху вниз, срабатывает первая истинная. Если ни одна не подошла и ИНАЧЕ не указано, поле получает NULL.

Конструкция Когда-Тогда работает одинаково во всех конфигурациях на платформе 8.3: БП, ЗУП, УТ, ERP и самописных. Её пишут в отчётах, обработках и вычисляемых полях СКД, чтобы подписать значение, посчитать сумму по условию, заменить NULL или разобрать составной тип регистратора.

Синтаксис ВЫБОР КОГДА ТОГДА и первый пример

Полная форма выглядит так:

ВЫБОР
    КОГДА <логическое выражение 1> ТОГДА <выражение 1>
    КОГДА <логическое выражение 2> ТОГДА <выражение 2>
    ИНАЧЕ <выражение по умолчанию>
КОНЕЦ

Английский вариант тот же: CASE WHEN … THEN … ELSE … END. Ключевые слова можно писать в любом регистре.

Правила, которые стоит держать в голове:

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

В 1С нет «простой» формы ВЫБОР Поле КОГДА 1 ТОГДА …, как в некоторых СУБД. Поле приходится повторять в каждой ветке: КОГДА Поле = 1 ТОГДА ….

Пример: разбить контрагентов по типу.

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

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

Булево поле можно ставить в условие без сравнения: КОГДА Номенклатура.ЭтоГруппа ТОГДА "Группа". Запись = ИСТИНА тоже сработает, но ничего не добавляет.

Где в запросе допустима конструкция Когда-Тогда

Часть запросаПримерЗамечание
ВЫБРАТЬвычисляемая колонкасамое частое применение
ГДЕГДЕ ВЫБОР … КОНЕЦ > 0работает, но мешает индексам
СГРУППИРОВАТЬ ПОгруппировка по «корзинам»выражение повторяют целиком, псевдоним не подходит
УПОРЯДОЧИТЬ ПОсвой порядок строкможно сослаться на псевдоним из ВЫБРАТЬ
ИМЕЮЩИЕусловие на агрегатвнутри — агрегатные функции
условие СОЕДИНЕНИЕ … ПОветвление связизаметно тормозит, лучше обходить

Нестандартная сортировка — полезный приём. Например, сначала просроченные заказы, потом сегодняшние, потом остальные:

УПОРЯДОЧИТЬ ПО
    ВЫБОР
        КОГДА Заказы.ДатаОтгрузки < &НачалоДня ТОГДА 1
        КОГДА Заказы.ДатаОтгрузки < &КонецДня ТОГДА 2
        ИНАЧЕ 3
    КОНЕЦ,
    Заказы.Дата

Когда-Тогда на виртуальных таблицах: остатки, обороты, срез последних

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

Имена регистров ниже условные: в каждой конфигурации они свои. Подставьте свой регистр из дерева метаданных.

Статус остатка на складе

ВЫБРАТЬ
    Остатки.Номенклатура,
    Остатки.КоличествоОстаток,
    ВЫБОР
        КОГДА Остатки.КоличествоОстаток < 0 ТОГДА "Минус"
        КОГДА Остатки.КоличествоОстаток = 0 ТОГДА "Нет"
        КОГДА Остатки.КоличествоОстаток < 10 ТОГДА "Мало"
        ИНАЧЕ "Достаточно"
    КОНЕЦ КАК Статус
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Остатки

Ветка = 0 сработает редко: виртуальная таблица остатков не возвращает строки с нулевым остатком по всем ресурсам. Зато в связке с левым соединением к справочнику она нужна — об этом в разделе про NULL.

Срез последних и пустая цена

Типичная задача — показать цену из регистра сведений, а при её отсутствии подставить ноль или пометку.

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

Отбор по типу цен стоит внутри параметров СрезПоследних. Если перенести его в ГДЕ, левое соединение молча превратится во внутреннее: строки без цены отсеются условием на NULL.

Приход и расход из таблицы движений

Когда виртуальной таблицы оборотов мало (например, нужны отдельные колонки по видам операций), читают физическую таблицу регистра и разбивают её ВЫБОР-ом:

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

Отбор по Активность обязателен: неактивные движения виртуальные таблицы пропускают сами, а физическая таблица — нет.

Вложенные ВЫБОР и порядок проверки условий в конструкции Когда-Тогда

Ветки проверяются строго сверху вниз. Как только условие истинно, остальные не смотрятся. Поэтому узкие условия ставят выше широких.

Ошибка порядка:

ВЫБОР
    КОГДА Сумма > 10000 ТОГДА "Крупная"
    КОГДА Сумма > 100000 ТОГДА "Очень крупная"   // никогда не сработает
    ИНАЧЕ "Обычная"
КОНЕЦ

Сумма 150 000 попадёт в первую ветку, и «Очень крупная» не появится ни разу. Достаточно поменять строки местами.

Вложенность

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

ВЫБОР
    КОГДА Документ.Организация = &ОсновнаяОрганизация ТОГДА
        ВЫБОР
            КОГДА Документ.СуммаДокумента >= 100000 ТОГДА "Основная, крупная"
            ИНАЧЕ "Основная, обычная"
        КОНЕЦ
    ИНАЧЕ "Прочие"
КОНЕЦ

Часто вложенность заменяется одной плоской конструкцией с составными условиями — так проще проверять:

ВЫБОР
    КОГДА Документ.Организация = &ОсновнаяОрганизация
        И Документ.СуммаДокумента >= 100000 ТОГДА "Основная, крупная"
    КОГДА Документ.Организация = &ОсновнаяОрганизация ТОГДА "Основная, обычная"
    ИНАЧЕ "Прочие"
КОНЕЦ

Приоритет И, ИЛИ, НЕ

Внутри условия НЕ выполняется раньше И, а И — раньше ИЛИ. Запись КОГДА А ИЛИ Б И В читается как А ИЛИ (Б И В).

Если смешиваете И и ИЛИ в одной ветке, ставьте скобки всегда. Ошибка приоритета не вызывает сообщения: запрос выполнится, но вернёт не те строки.

ВЫБОР внутри агрегатных функций: суммы и количество по условию

Связка СУММА(ВЫБОР …) — главный рабочий приём для отчётов «в колонки». Один проход по таблице вместо нескольких подзапросов.

ЗадачаШаблон
Сумма по условиюСУММА(ВЫБОР КОГДА <условие> ТОГДА Сумма ИНАЧЕ 0 КОНЕЦ)
Число строк по условиюСУММА(ВЫБОР КОГДА <условие> ТОГДА 1 ИНАЧЕ 0 КОНЕЦ)
Число разных значений по условиюКОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВЫБОР КОГДА <условие> ТОГДА Контрагент КОНЕЦ)
Максимум по условиюМАКСИМУМ(ВЫБОР КОГДА <условие> ТОГДА Дата КОНЕЦ)

В третьей и четвёртой строке ИНАЧЕ опущено намеренно. Агрегатные функции пропускают NULL, поэтому неподходящие строки не попадут в подсчёт. Если написать ИНАЧЕ 0 внутри КОЛИЧЕСТВО(РАЗЛИЧНЫЕ …), ноль посчитается как отдельное значение и итог вырастет на единицу.

Пример: продажи по месяцам квартала в трёх колонках.

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

Агрегат внутри условия

Обратная ситуация — условие на уже посчитанную сумму: ВЫБОР КОГДА СУММА(Продажи.Сумма) > 1000000 ТОГДА "VIP" ИНАЧЕ "Обычный" КОНЕЦ. Так можно, но все поля внутри конструкции должны быть либо в агрегатах, либо в СГРУППИРОВАТЬ ПО.

Если внутри ВЫБОР стоит негруппируемое поле без агрегата, платформа остановит запрос с ошибкой вида Поле не входит в группу. Лечится двумя способами: добавить выражение в группировку целиком или обернуть поле в агрегат.

ВЫБОР в группировке

Группировка по «корзинам» требует повторить выражение в СГРУППИРОВАТЬ ПО дословно:

ВЫБРАТЬ
    ВЫБОР
        КОГДА Док.СуммаДокумента < 10000 ТОГДА "до 10 тыс."
        КОГДА Док.СуммаДокумента < 100000 ТОГДА "10–100 тыс."
        ИНАЧЕ "от 100 тыс."
    КОНЕЦ КАК Диапазон,
    КОЛИЧЕСТВО(*) КАК Документов
ИЗ
    Документ.РеализацияТоваровУслуг КАК Док
СГРУППИРОВАТЬ ПО
    ВЫБОР
        КОГДА Док.СуммаДокумента < 10000 ТОГДА "до 10 тыс."
        КОГДА Док.СуммаДокумента < 100000 ТОГДА "10–100 тыс."
        ИНАЧЕ "от 100 тыс."
    КОНЕЦ

Чтобы не дублировать длинный текст, выражение считают во временной таблице, а группируют уже по её полю.

Когда-Тогда и NULL: замена пустых значений и тип результата

NULL в запросах появляется в трёх случаях: левое или полное соединение без пары, ВЫБОР без ИНАЧЕ и обращение через точку к реквизиту, которого нет у конкретного типа составной ссылки.

Проверка на NULL

Сравнение = NULL не работает : оно никогда не бывает истинным. Правильно — ЕСТЬ NULL и НЕ … ЕСТЬ NULL.

ЗадачаВариант с ВЫБОРКороткий вариант
Заменить NULL нулёмВЫБОР КОГДА Поле ЕСТЬ NULL ТОГДА 0 ИНАЧЕ Поле КОНЕЦЕСТЬNULL(Поле, 0)
Подпись вместо пустоВЫБОР КОГДА Поле ЕСТЬ NULL ТОГДА "нет данных" ИНАЧЕ "есть" КОНЕЦ—
Первое непустое из двухВЫБОР КОГДА А ЕСТЬ NULL ТОГДА Б ИНАЧЕ А КОНЕЦЕСТЬNULL(А, Б)

Для простой замены ЕСТЬNULL короче и читается легче. ВЫБОР нужен, когда результат отличается от исходного поля: подпись, флаг, другое поле.

Если поле в условии равно NULL, сравнения вроде Поле > 0 или Поле = 5 дают «ложь», и строка уходит в ИНАЧЕ. Это частая причина «лишних» строк в ветке по умолчанию: остаток не нулевой, его просто нет.

Пустое значение и NULL — не одно и то же

Незаполненный реквизит-ссылка — это пустая ссылка, а не NULL. Проверяют его так: КОГДА Док.Контрагент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка). Пустая строка — "", пустая дата — ДАТАВРЕМЯ(1, 1, 1).

Тип результата

Ветки могут возвращать значения разных типов — тогда поле получит составной тип. Для вывода в отчёт это допустимо. Для расчётов — нет: СУММА по полю, где в одной ветке число, а в другой строка, выполнить не удастся.

Правило простое: во всех ветках, включая ИНАЧЕ, возвращайте один тип. Для чисел — 0, для строк — "", для ссылок — ЗНАЧЕНИЕ(… .ПустаяСсылка).

Составной тип регистратора

Поле Регистратор в регистрах ссылается на разные документы. Разбор по типу делается через ССЫЛКА:

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

Если нужен реквизит конкретного документа, его достают через ВЫРАЗИТЬ: ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент. Для документов другого типа вернётся NULL.

Производительность запросов с ВЫБОР КОГДА ТОГДА

Сама конструкция дешёвая: это вычисление по строке, уже прочитанной из базы. Медленным запрос делает не ВЫБОР, а место, куда его поставили.

Что тормозит и чем заменить:

  • 🐢 ВЫБОР в ГДЕ по индексированному полю. СУБД не может использовать индекс по выражению. Перепишите условие через И/ИЛИ или разбейте на два запроса с ОБЪЕДИНИТЬ ВСЕ.
  • 🐢 ВЫБОР в условии соединения. Связь «по ветвлению» часто превращается в перебор вложенными циклами. Лучше сделать два соединения и выбрать поле в ВЫБРАТЬ.
  • 🐢 Обращение через точку от составного поля. Движения.Регистратор.Дата внутри ВЫБОР порождает соединения со всеми документами-регистраторами. Используйте ВЫРАЗИТЬ к нужному типу или поле Период.
  • 🐢 Условия на измерения в ГДЕ после виртуальной таблицы. Перенесите их в параметры таблицы, а ВЫБОР оставьте на ресурсы.
  • 🐢 Один и тот же длинный ВЫБОР в пяти местах. Посчитайте его один раз во временной таблице.

Пример переписывания условия из ГДЕ. Было:

ГДЕ
    ВЫБОР
        КОГДА &ТолькоОрганизация ТОГДА Док.Организация = &Организация
        ИНАЧЕ ИСТИНА
    КОНЕЦ

Такую запись трудно читать, и оптимизатору она мешает. Стало:

ГДЕ
    (НЕ &ТолькоОрганизация ИЛИ Док.Организация = &Организация)

Ещё надёжнее — собрать текст запроса в коде и добавлять условие только когда флаг установлен. Тогда СУБД получает простой запрос и строит по нему нормальный план.

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

Частые ошибки в конструкции Когда-Тогда

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

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

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