При составлении сложных отчетов и выборок разработчики нередко сталкиваются с необходимостью вычислить значение поля по условию, зависящему от данных другой таблицы. Естественным желанием становится поместить вложенный подзапрос непосредственно в ветку КОГДА оператора ВЫБОР. Однако при попытке написать прямое сравнение платформа выдает синтаксическую ошибку «ожидается выражение выбрать». Разберем, почему возникает эта ошибка, как работает синтаксис подзапросов в ветвлениях и какие способы решения являются наиболее производительными и правильными с точки зрения стандартов разработки 1С.
Проанализируем типовую ошибку, с которой сталкиваются разработчики при попытке написать выражение следующего вида:
ВЫБОР
КОГДА (ВЫБРАТЬ 1 ИЗ Константы) = 1 ТОГДА
Поле1
ИНАЧЕ
Поле2
КОНЕЦ
Язык запросов 1С не поддерживает так называемые скалярные подзапросы в роли операндов операций сравнения (=, <>, >, <). Конструктор и парсер запросов платформы ожидают, что результат подзапроса является таблицей, а не элементарным значением. Следовательно, прямое приравнивание выборки к логическому значению ИСТИНА или числу синтаксически недопустимо.
Кроме того, если в запросе используется секция СГРУППИРОВАТЬ ПО, применение подзапросов в списке выборки приведет к ошибке группировки: каждое поле выборки обязано быть либо частью группировки, либо агрегатной функцией (СУММА, МАКСИМУМ и др.), а включить вложенный запрос в предложение СГРУППИРОВАТЬ ПО невозможно.
Единственный штатный способ задействовать подзапрос внутри логического условия КОГДА — это проверка вхождения значения в выборку через предикат В (или IN). При этом подзапрос должен возвращать ровно одну колонку значений.
Посмотрим на рабочий пример синтаксической конструкции:
ВЫБОР
КОГДА ИСТИНА В (ВЫБРАТЬ ИСТИНА ИЗ РегистрСведений.ВерсииПодсистем) ТОГДА
МАКСИМУМ(ВложенныйЗапросНДФЛ.Сумма)
ИНАЧЕ
0
КОНЕЦ
Данная конструкция успешно проходит проверку синтаксиса и компилируется платформой. Рассмотрим подробнее, как можно эмулировать конструкцию EXISTS, которая поддерживается многими классическими СУБД, но отсутствует как отдельное ключевое слово в 1С:
ВЫБОР
КОГДА 1 В (ВЫБРАТЬ ПЕРВЫЕ 1 1 ИЗ Документ.РеализацияТоваровУслуг КАК Реализация ГДЕ Реализация.Проведен) ТОГДА
"Документы проведены"
ИНАЧЕ
"Проведенных документов нет"
КОНЕЦ
Ограничение ключевым словом ПЕРВЫЕ 1 позволяет несколько ускорить получение результата, однако на реальных базах данных такой подход всё равно несет в себе архитектурные риски.
Несмотря на то, что использование предиката В (ВЫБРАТЬ ...) внутри ВЫБОР КОГДА технически допустимо, на практике это часто приводит к критическому замедлению работы системы. Выясним причину такого поведения:
ВЫБОР рассчитывается для каждой результирующей строки выборки. Если оптимизатор СУБД построит некорректный план, подзапрос будет физически выполняться заново для каждой записи таблицы, что создает квадратичную сложность ($O(N \times M)$).ТОГДА и ИНАЧЕ возвращают типы разной длины или разного состава, платформа может назначить полю составной тип или тип с неограниченной длиной строки, что вызовет проблемы при дальнейшей записи во временные таблицы. Для предотвращения этого конструкцию оборачивают в функцию ВЫРАЗИТЬ.Разберем надежные и производительные способы реализации той же бизнес-логики без использования подзапросов в ветвлениях.
Если условие не связано с полями текущей строки запроса (то есть проверяется общее состояние базы данных), оптимально вычислить его заранее отдельным быстрым запросом во встроенном языке и передать результат как булев параметр:
ЗапросПроверки = Новый Запрос;
ЗапросПроверки.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| 1 КАК Поле1
|ИЗ
| РегистрСведений.ВерсииПодсистем КАК ВерсииПодсистем";
ЕстьДанные = НЕ ЗапросПроверки.Выполнить().Пустой();
ОсновнойЗапрос = Новый Запрос;
ОсновнойЗапрос.Текст =
"ВЫБРАТЬ
| ВЫБОР
| КОГДА &УсловиеВыполнено ТОГДА
| МАКСИМУМ(Начисления.Сумма)
| ИНАЧЕ
| 0
| КОНЕЦ КАК РезультатСумма
|ИЗ
| ТаблицаНачислений КАК Начисления
|СГРУППИРОВАТЬ ПО
| Начисления.Сотрудник";
ОсновнойЗапрос.УстановитьПараметр("УсловиеВыполнено", ЕстьДанные);
Этот метод гарантирует, что проверка будет выполнена ровно один раз, а СУБД сможет максимально эффективно оптимизировать план основного запроса.
Если условие должно учитывать связь с данными конкретной строки выборки, классическим и самым производительным паттерном 1С является ЛЕВОЕ СОЕДИНЕНИЕ:
ЛЕВОЕ СОЕДИНЕНИЕ.ВЫБОР КОГДА проверяем наличие связанных данных через оператор ЕСТЬ NULL.Посмотрим на практическую реализацию:
ВЫБРАТЬ
ОсновнаяТаблица.Сотрудник КАК Сотрудник,
ВЫБОР
КОГДА СвязанныеДанные.Идентификатор ЕСТЬ NULL ТОГДА
0
ИНАЧЕ
МАКСИМУМ(ОсновнаяТаблица.Сумма)
КОНЕЦ КАК СуммаРасчета
ИЗ
ВложенныйЗапросНачисления КАК ОсновнаяТаблица
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДополнительныеДанные КАК СвязанныеДанные
ПО ОсновнаяТаблица.Сотрудник = СвязанныеДанные.Сотрудник
СГРУППИРОВАТЬ ПО
ОсновнаяТаблица.Сотрудник,
ВЫБОР
КОГДА СвязанныеДанные.Идентификатор ЕСТЬ NULL ТОГДА 0
ИНАЧЕ 1
КОНЕЦ
Если в блоке ВЫБОР дублируются однотипные действия для разных значений одного поля, вместо нескольких веток КОГДА следует объединять условия в один список:
ВЫБОР
КОГДА ВложенныйЗапросНачисления.ВидРасчета В (
ЗНАЧЕНИЕ(ПланВидовРасчета.ОсновныеНачисленияОрганизаций.ОкладПоДням),
ЗНАЧЕНИЕ(ПланВидовРасчета.ОсновныеНачисленияОрганизаций.ОкладПоЧасам),
ЗНАЧЕНИЕ(ПланВидовРасчета.ДополнительныеНачисленияОрганизаций.ДоговорПодряда),
&ОкладПоДнямАкционеры
) ТОГДА
МАКСИМУМ(ВложенныйЗапросНДФЛ.Сумма)
ИНАЧЕ
0
КОНЕЦ
Такой подход делает текст запроса компактным, предотвращает ошибки дублирования логики и снижает нагрузку на оптимизатор запросов при построении плана выполнения.