Можно ли использовать вложенный запрос внутри конструкции ВЫБОР КОГДА в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При составлении сложных отчетов и выборок разработчики нередко сталкиваются с необходимостью вычислить значение поля по условию, зависящему от данных другой таблицы. Естественным желанием становится поместить вложенный подзапрос непосредственно в ветку КОГДА оператора ВЫБОР. Однако при попытке написать прямое сравнение платформа выдает синтаксическую ошибку «ожидается выражение выбрать». Разберем, почему возникает эта ошибка, как работает синтаксис подзапросов в ветвлениях и какие способы решения являются наиболее производительными и правильными с точки зрения стандартов разработки 1С.

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

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


ВЫБОР
    КОГДА (ВЫБРАТЬ 1 ИЗ Константы) = 1 ТОГДА
        Поле1
    ИНАЧЕ
        Поле2
КОНЕЦ

Язык запросов 1С не поддерживает так называемые скалярные подзапросы в роли операндов операций сравнения (=, <>, >, <). Конструктор и парсер запросов платформы ожидают, что результат подзапроса является таблицей, а не элементарным значением. Следовательно, прямое приравнивание выборки к логическому значению ИСТИНА или числу синтаксически недопустимо.

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

Синтаксис использования подзапроса через оператор вхождения В (IN)

Единственный штатный способ задействовать подзапрос внутри логического условия КОГДА — это проверка вхождения значения в выборку через предикат В (или IN). При этом подзапрос должен возвращать ровно одну колонку значений.

Посмотрим на рабочий пример синтаксической конструкции:


ВЫБОР
    КОГДА ИСТИНА В (ВЫБРАТЬ ИСТИНА ИЗ РегистрСведений.ВерсииПодсистем) ТОГДА
        МАКСИМУМ(ВложенныйЗапросНДФЛ.Сумма)
    ИНАЧЕ
        0
КОНЕЦ

Данная конструкция успешно проходит проверку синтаксиса и компилируется платформой. Рассмотрим подробнее, как можно эмулировать конструкцию EXISTS, которая поддерживается многими классическими СУБД, но отсутствует как отдельное ключевое слово в 1С:


ВЫБОР
    КОГДА 1 В (ВЫБРАТЬ ПЕРВЫЕ 1 1 ИЗ Документ.РеализацияТоваровУслуг КАК Реализация ГДЕ Реализация.Проведен) ТОГДА
        "Документы проведены"
    ИНАЧЕ
        "Проведенных документов нет"
КОНЕЦ

Ограничение ключевым словом ПЕРВЫЕ 1 позволяет несколько ускорить получение результата, однако на реальных базах данных такой подход всё равно несет в себе архитектурные риски.

Проблемы производительности и антипаттерны

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

  1. Вычисление для каждой строки (N+1). Конструкция ВЫБОР рассчитывается для каждой результирующей строки выборки. Если оптимизатор СУБД построит некорректный план, подзапрос будет физически выполняться заново для каждой записи таблицы, что создает квадратичную сложность ($O(N \times M)$).
  2. Проверка независимых условий. Проверять статическое условие (например, наличие записей в регистре или значение константы) на уровне каждой строки большого запроса — грубая ошибка оптимизации.
  3. Неопределенные типы полей. Если ветви ТОГДА и ИНАЧЕ возвращают типы разной длины или разного состава, платформа может назначить полю составной тип или тип с неограниченной длиной строки, что вызовет проблемы при дальнейшей записи во временные таблицы. Для предотвращения этого конструкцию оборачивают в функцию ВЫРАЗИТЬ.

Оптимальные альтернативы

Разберем надежные и производительные способы реализации той же бизнес-логики без использования подзапросов в ветвлениях.

Способ 1: Предварительный расчет и передача через параметры

Если условие не связано с полями текущей строки запроса (то есть проверяется общее состояние базы данных), оптимально вычислить его заранее отдельным быстрым запросом во встроенном языке и передать результат как булев параметр:


ЗапросПроверки = Новый Запрос;
ЗапросПроверки.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |	1 КАК Поле1
    |ИЗ
    |	РегистрСведений.ВерсииПодсистем КАК ВерсииПодсистем";
ЕстьДанные = НЕ ЗапросПроверки.Выполнить().Пустой();

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

Этот метод гарантирует, что проверка будет выполнена ровно один раз, а СУБД сможет максимально эффективно оптимизировать план основного запроса.

Способ 2: Левое соединение с временной таблицей

Если условие должно учитывать связь с данными конкретной строки выборки, классическим и самым производительным паттерном 1С является ЛЕВОЕ СОЕДИНЕНИЕ:

  1. Подготавливаем ключевые поля и флаги во временной таблице, индексируя ее по полям связи.
  2. Присоединяем временную таблицу к основной через ЛЕВОЕ СОЕДИНЕНИЕ.
  3. В выражении ВЫБОР КОГДА проверяем наличие связанных данных через оператор ЕСТЬ NULL.

Посмотрим на практическую реализацию:


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

Способ 3: Группировка условий в списке значений

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


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

Такой подход делает текст запроса компактным, предотвращает ошибки дублирования логики и снижает нагрузку на оптимизатор запросов при построении плана выполнения.

← На главную