Как написать ВЫБОР с несколькими условиями в запросе 1С 8.3

· 10 мин

Содержание

В 1С ВЫБОР КОГДА с условием И пишется так: ВЫБОР КОГДА Условие1 И Условие2 ТОГДА Значение ИНАЧЕ Другое КОНЕЦ. Ветка срабатывает, только если истинны обе части. Условия веток проверяются сверху вниз, первая истинная ветка даёт результат, остальные не смотрятся.

Оператор ВЫБОР есть только в языке запросов и в выражениях системы компоновки данных (СКД). Во встроенном языке его нет: там используют Если … ИначеЕсли … КонецЕсли или тернарный оператор ?(Условие, Значение1, Значение2). Всё, что ниже, работает в платформе 8.3 в любой конфигурации: БП 3.0, ЗУП 3.1, УТ 11 и самописных. Имена таблиц и реквизитов в примерах условные — подставьте свои.

Синтаксис ВЫБОР КОГДА и как платформа проверяет условия

Полная форма оператора:

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

Что нужно помнить о каждой части:

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

Порядок веток имеет значение. Платформа идёт сверху вниз и останавливается на первой истинной ветке. Поэтому узкие условия ставят выше широких.

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

Если поменять первые две строки местами, сумма 1 500 000 попадёт в «Средний»: условие >= 100000 для неё тоже истинно и стоит раньше.

ВЫБОР можно ставить почти в любую часть запроса: в список полей ВЫБРАТЬ, в ГДЕ, внутрь агрегатных функций (СУММА, КОЛИЧЕСТВО), в УПОРЯДОЧИТЬ ПО, в условие соединения. Это обычное выражение, которое возвращает значение.

Условие И в ветке КОГДА

Связка И требует, чтобы все части были истинны:

ВЫБОР
    КОГДА Документ.Проведен И Документ.СуммаДокумента > 0
        ТОГДА "В работе"
    ИНАЧЕ "Пропустить"
КОНЕЦ КАК Статус

Частей может быть сколько угодно: КОГДА А И Б И В. Читается проще, если каждую часть перенести на новую строку и выровнять.

Логическое значение как результат

Когда нужно поле-флаг «да/нет», пишут ветки с ИСТИНА и ЛОЖЬ:

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

Такое поле удобно потом использовать в отборе СКД или в условном оформлении.

Приоритет И над ИЛИ: где без скобок ВЫБОР ошибается

В языке запросов 1С, как и в SQL, порядок такой: сначала НЕ, затем И, затем ИЛИ. Это главная причина «неправильных» результатов в ветках с несколькими условиями.

Пример. Нужно пометить продажи одному из двух складов на сумму больше 50 000:

// Неверно
КОГДА Склад = &СкладА ИЛИ Склад = &СкладБ И Сумма > 50000 ТОГДА "Проверить"

Платформа прочитает это как Склад = &СкладА ИЛИ (Склад = &СкладБ И Сумма > 50000). В итоге все продажи склада А попадут в «Проверить», даже на 100 рублей.

// Верно
КОГДА (Склад = &СкладА ИЛИ Склад = &СкладБ) И Сумма > 50000 ТОГДА "Проверить"

Ещё короче — через В:

КОГДА Склад В (&СкладА, &СкладБ) И Сумма > 50000 ТОГДА "Проверить"
ЗаписьКак читает платформаКогда истинно
А ИЛИ Б И ВА ИЛИ (Б И В)А истинно — сразу ветка
(А ИЛИ Б) И Вкак написаноВ обязательно
НЕ А И Б(НЕ А) И БА ложно и Б истинно
НЕ (А И Б)как написанохотя бы одно ложно

Правило простое: смешали И с ИЛИ в одной ветке — ставьте скобки, даже если уверены в приоритете. Через полгода этот запрос будет читать другой человек, и скобки избавят его от гаданий.

Когда лучше разбить на несколько КОГДА

Длинное условие с пятью ИЛИ часто удобнее развести по веткам с одинаковым результатом:

ВЫБОР
    КОГДА Сумма > 50000 И Склад = &СкладА ТОГДА "Проверить"
    КОГДА Сумма > 50000 И Склад = &СкладБ ТОГДА "Проверить"
    КОГДА Контрагент.НеРезидент ТОГДА "Проверить"
    ИНАЧЕ "ОК"
КОНЕЦ

Каждую ветку легко закомментировать при отладке. Результат тот же, что у одной ветки с ИЛИ и скобками.

Проверьте себя
Как платформа прочитает условие А ИЛИ Б И В?
Что вернёт ВЫБОР без ИНАЧЕ, если ни одна ветка не подошла?
Поле Цена после левого соединения равно NULL. Какая ветка сработает: КОГДА Цена > 0 ТОГДА 1 КОГДА НЕ Цена > 0 ТОГДА 2 ИНАЧЕ 3?

NULL и пустые значения в условиях ВЫБОР

NULL появляется в запросе чаще, чем кажется:

  • 🔸 после ЛЕВОЕ СОЕДИНЕНИЕ, когда справа нет пары;
  • 🔸 при обращении через точку к реквизиту составного типа, если значение другого типа;
  • 🔸 в ветке ВЫБОР без ИНАЧЕ;
  • 🔸 в итогах виртуальных таблиц, если движений не было.

Любое сравнение с NULL не даёт истину. Поэтому КОГДА Цена > 0 и КОГДА НЕ Цена > 0 при NULL обе не сработают, и запрос уйдёт в ИНАЧЕ. Это главная ловушка при условии с НЕ.

Проверять NULL нужно только так:

КОГДА Цены.Цена ЕСТЬ NULL ТОГДА 0

Запись Цены.Цена = NULL — ошибка: истиной она не станет никогда.

Для замены NULL на значение есть короткая функция ЕСТЬNULL. Две записи ниже равнозначны:

ВЫБОР КОГДА Цены.Цена ЕСТЬ NULL ТОГДА 0 ИНАЧЕ Цены.Цена КОНЕЦ
ЕСТЬNULL(Цены.Цена, 0)

NULL, пустая ссылка, пустая строка — это разное

Что проверяемКак записать условие
нет записи после соединенияПоле ЕСТЬ NULL
ссылка не заполненаПоле = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
строка не заполненаПоле = ""
дата не заполненаПоле = ДАТАВРЕМЯ(1, 1, 1)
число не заполненоПоле = 0

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

КОГДА Партнер.Ссылка ЕСТЬ NULL
    ИЛИ Партнер.Ссылка = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
    ТОГДА "Не указан"

Здесь ИЛИ один, без И, поэтому скобки не нужны.

Защита от деления на ноль

Цена за единицу в отчёте — частый случай, где ВЫБОР спасает запрос:

ВЫБОР
    КОГДА Остатки.КоличествоОстаток = 0 ТОГДА 0
    ИНАЧЕ Остатки.СуммаОстаток / Остатки.КоличествоОстаток
КОНЕЦ КАК СредняяЦена

Если количество может быть ещё и NULL, оберните его: ЕСТЬNULL(Остатки.КоличествоОстаток, 0) = 0.

Тип результата и сравнение ссылок

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

Привести результат к одному типу помогает ВЫРАЗИТЬ:

ВЫРАЗИТЬ(
    ВЫБОР
        КОГДА Док.Комментарий = "" ТОГДА "Без комментария"
        ИНАЧЕ Док.Комментарий
    КОНЕЦ КАК СТРОКА(100)) КАК Примечание

Ссылки сравнивают через ЗНАЧЕНИЕ() или параметр, а не со строкой:

КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход) ТОГДА 1

Для полей составного типа, например Регистратор, тип проверяют оператором ССЫЛКА:

КОГДА Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг ТОГДА "Продажа"

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

Сам по себе ВЫБОР в списке полей почти ничего не стоит: он считается для строк, которые уже отобраны. Проблемы начинаются, когда его ставят туда, где СУБД нужно искать данные.

ВЫБОР в ГДЕ мешает индексам

Частый приём для «необязательного отбора»:

ГДЕ
    ВЫБОР
        КОГДА &ОтборПоКонтрагенту ТОГДА Продажи.Контрагент = &Контрагент
        ИНАЧЕ ИСТИНА
    КОНЕЦ

Работает, но СУБД не может использовать индекс по Контрагент: условие спрятано внутри выражения. На таблице в миллион строк это полный просмотр.

Как лучше:

  1. Собирать текст запроса во встроенном языке. Если отбор не нужен — условия в тексте просто нет.
  2. Для виртуальных таблиц регистров отбор передавать в параметры таблицы, а не в ГДЕ.
  3. Если запрос один на все случаи, хотя бы писать (НЕ &ОтборПоКонтрагенту ИЛИ Продажи.Контрагент = &Контрагент) — его проще читать и отлаживать.
ТекстОтбора = "";
Если ЗначениеЗаполнено(Контрагент) Тогда
    ТекстОтбора = " И Продажи.Контрагент = &Контрагент";
КонецЕсли;
Запрос.Текст = ТекстЗапроса + ТекстОтбора;
Запрос.УстановитьПараметр("Контрагент", Контрагент);

Здесь ТекстЗапроса должен заканчиваться условием ГДЕ ИСТИНА или другим условием, к которому можно дописать И.

Разыменование внутри веток

Каждое обращение через точку (Контрагент.ИНН, Регистратор.Склад) — это неявное соединение с другой таблицей. Если в трёх ветках ВЫБОР обращаться к реквизитам Регистратор, а он ссылается на 40 видов документов, запрос соединится со всеми сорока таблицами.

Решение — указать тип явно:

КОГДА Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
    ТОГДА ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Склад

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

Частые ошибки в ВЫБОР КОГДА и как их исправить

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

Ещё две ошибки, которые не дают сообщения, а просто искажают цифры:

  • ❌ Широкое условие выше узкого. Ветки проверяются по порядку, нижняя никогда не сработает. Сортируйте ветки от самого строгого условия к общему.
  • ❌ Попытка использовать ВЫБОР в коде модуля. Во встроенном языке такой конструкции нет, будет ошибка компиляции. Пишите ?() или Если.

Примеры ВЫБОР с условием И для отчётов

Приход и расход в одном запросе

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

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

Сегментация клиентов по двум признакам

Нужно выделить крупных клиентов с просрочкой. Два условия через И:

ВЫБОР
    КОГДА Долги.Сумма > 500000 И Долги.ДнейПросрочки > 30
        ТОГДА "Крупный, просрочка"
    КОГДА Долги.Сумма > 500000
        ТОГДА "Крупный"
    КОГДА Долги.ДнейПросрочки > 30
        ТОГДА "Просрочка"
    ИНАЧЕ "Норма"
КОНЕЦ КАК Группа

Первой стоит ветка с обоими условиями — она самая строгая. Если поставить её третьей, клиент с долгом 600 000 и просрочкой 45 дней попадёт в «Крупный».

Проверка на числах:

СуммаДней просрочкиГруппа
600 00045Крупный, просрочка
600 00010Крупный
80 00045Просрочка
80 00010Норма

Свой порядок сортировки

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

УПОРЯДОЧИТЬ ПО
    ВЫБОР
        КОГДА Заказы.Статус = &СтатусНовый ТОГДА 1
        КОГДА Заказы.Статус = &СтатусВРаботе ТОГДА 2
        ИНАЧЕ 3
    КОНЕЦ,
    Заказы.Дата

Вычисляемое поле в СКД

В схеме компоновки данных то же выражение пишут на вкладке вычисляемых полей. Язык выражений СКД понимает конструкцию Выбор Когда … Тогда … Иначе … Конец и те же операторы И, ИЛИ, НЕ. Скобки и правило приоритета работают так же, как в запросе.

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

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

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