ВЫБОР, ЕСТЬNULL и приведение типов

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

ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ — это условное выражение внутри запроса: оно вычисляет разные значения поля в зависимости от условия, не выходя в код на встроенном языке.

Часто данные из базы нужно не просто достать, а слегка «причесать» прямо в запросе: подставить текст вместо кода, заменить пустое значение нулём, привести поле составного типа к нужной ссылке. Делать это в цикле после запроса — медленно и многословно. Язык запросов 1С умеет всё перечисленное сам. Разберём три ключевые конструкции: ВЫБОР, ЕСТЬNULL и ВЫРАЗИТЬ.

Зачем это нужно на практике

Пример из жизни: отчёт по заказам, где нужна колонка «Статус оплаты» — «Оплачен», «Частично» или «Не оплачен» в зависимости от суммы оплаты. Или ведомость остатков, где по товарам без движений вместо пустоты должен стоять честный ноль. Или журнал, где в одном поле лежат ссылки разных типов, а нам нужен реквизит, который есть только у одного из них. Все три задачи решаются выражениями в запросе — быстро и в одном месте, без постобработки в коде.

ВЫБОР: условие внутри запроса

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

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

Результат:

Заказ           Сумма    Оплачено   СтатусОплаты
Заказ №12       50000    50000      Оплачен
Заказ №13       80000    30000      Частично
Заказ №14       12000    0          Не оплачен

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

ВЫБОР по значению поля

Есть и короткая форма — сравнение конкретного поля с вариантами. Она удобна, когда «раскрашиваете» перечисление в текст:

ВЫБОР Заказы.Приоритет
    КОГДА ЗНАЧЕНИЕ(Перечисление.Приоритеты.Высокий)  ТОГДА "!!! Срочно"
    КОГДА ЗНАЧЕНИЕ(Перечисление.Приоритеты.Обычный) ТОГДА "В план"
    ИНАЧЕ "Когда-нибудь"
КОНЕЦ КАК Метка

Функция ЗНАЧЕНИЕ(...) подставляет в запрос предопределённый элемент (значение перечисления, пустую ссылку и т.п.) без параметра — это правильный способ сравнивать с константами метаданных прямо в тексте запроса.

ЕСТЬNULL: замена пустот после левого соединения

Когда вы делаете ЛЕВОЕ СОЕДИНЕНИЕ, у строк левой таблицы, которым не нашлось пары справа, поля правой таблицы становятся NULL — «значение отсутствует». NULL коварен: он не равен нулю, не равен пустой строке, и в арифметике «заражает» результат (число + NULL = NULL). Функция ЕСТЬNULL(поле, значение_по_умолчанию) подставляет запасное значение там, где пришёл NULL:

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

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

ВЫРАЗИТЬ: приведение ссылочных типов

В 1С встречаются поля составного типа — например, поле «Регистратор» движения может ссылаться и на «ЗаказПокупателя», и на «РеализациюТоваров». Пока платформа видит составной тип, она не даёт обратиться к реквизиту через точку — ведь у разных типов реквизиты разные. Функция ВЫРАЗИТЬ(поле КАК Тип) говорит: «здесь трактуй значение как ссылку именно этого типа», и после неё точечное обращение к реквизитам становится доступным:

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

Здесь мы сузили тип регистратора до РеализацияТоваров и достали её реквизит Склад. Условие ССЫЛКА Документ.РеализацияТоваров в ГДЕ оставляет только те строки, где регистратор действительно этого типа, — иначе ВЫРАЗИТЬ для «чужих» строк вернёт NULL. Приём «ВЫРАЗИТЬ + проверка ССЫЛКА» — стандартный способ работать с составными полями.

У ВЫРАЗИТЬ есть и вторая роль — задать длину строки или точность числа, например ВЫРАЗИТЬ(Комментарий КАК СТРОКА(100)). Это помогает, когда неограниченную строку нужно, скажем, сгруппировать (по полю неограниченной длины группировать нельзя).

Как это работает

ВЫБОР транслируется в конструкцию CASE WHEN языка SQL и вычисляется на стороне СУБД — поэтому он дёшев и не требует выгрузки данных в код. ЕСТЬNULL превращается в ISNULL/COALESCE: СУБД сама подставляет значение по умолчанию. А NULL появляется именно из-за механики соединений: левое (и полное) соединение сохраняет строки без пары, заполняя недостающие поля маркером «нет значения». ВЫРАЗИТЬ для ссылок — это, по сути, приведение типа (CAST) с проверкой: если фактический тип значения не совпал с запрошенным, вернётся NULL, а не ошибка. Понимание, что всё это выполняет СУБД, объясняет, почему причёсывать данные в запросе почти всегда выгоднее, чем в цикле после него.

Частые ошибки

  • Сравнение с NULL через «равно». Проверка Поле = NULL никогда не даёт «истина» — NULL ни с чем не равен. Для замены используйте ЕСТЬNULL, а для проверки на отсутствие — Поле ЕСТЬ NULL.
  • Разные типы в ветках ВЫБОР. Одна ветка возвращает строку, другая — число или ссылку. Результат либо приводится к неожиданному типу, либо запрос падает. Следите за единообразием веток.
  • ВЫРАЗИТЬ без проверки ССЫЛКА. Применили ВЫРАЗИТЬ ко всем строкам составного поля, включая «чужие» типы, — для них получили NULL и удивились пустотам. Ограничивайте выборку условием ССЫЛКА ТипДокумента.
  • Забыли ЕСТЬNULL после левого соединения. Посчитали СУММА по полю из левосоединённой таблицы и получили NULL или заниженный итог, потому что часть строк была пустой. Оборачивайте такие поля в ЕСТЬNULL(..., 0) до агрегации.

Итоги

  • ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ вычисляет значение поля по условию прямо в запросе; все ветки должны быть одного типа.
  • ЕСТЬNULL(поле, умолчание) заменяет NULL (частый гость после левого соединения) на осмысленное значение — обычно 0 или пустую строку.
  • NULL не равен нулю и «заражает» арифметику; сравнивать через = NULL нельзя, только ЕСТЬ NULL.
  • ВЫРАЗИТЬ(поле КАК Тип) приводит составную ссылку к конкретному типу, открывая доступ к её реквизитам; страхуйтесь условием ССЫЛКА.
  • Все три конструкции считаются на стороне СУБД — причёсывать данные в запросе выгоднее, чем в цикле после него.
Проверьте себя
1. Что вернёт функция ЕСТЬNULL(ОстаткиНаСкладе.КоличествоОстаток, 0) для товара, у которого после ЛЕВОГО СОЕДИНЕНИЯ нет строки в остатках?
ANULL
BПустую строку
C0
DОшибку выполнения запроса
2. Зачем нужна функция ВЫРАЗИТЬ при работе с полем составного (ссылочного) типа?
AЧтобы отсортировать результат по этому полю
BЧтобы привести ссылку к конкретному типу и получить доступ к его реквизитам через точку
CЧтобы заменить NULL на значение по умолчанию
DЧтобы объединить два запроса в пакет
3. Почему условие Поле = NULL в запросе 1С не работает как ожидается?
ANULL можно сравнивать только в секции ИМЕЮЩИЕ
BТакое сравнение никогда не даёт «истина», так как NULL не равен ничему; нужно Поле ЕСТЬ NULL
CNULL автоматически превращается в 0, поэтому условие всегда ложно
DОператор = запрещён в языке запросов 1С