К статье
Практический материал

Разработка и Конфигурация · 12 мин

Вложенный запрос как условие в 1С: синтаксис, примеры и ошибки

Как использовать вложенный запрос в условии В, НЕ В и В ИЕРАРХИИ в запросах 1С: синтаксис, примеры с подзапросами, типичные ошибки и проверка результата.

Вложенный запрос (подзапрос) в языке запросов 1С — это запрос, заключённый в круглые скобки внутри другого запроса. Одно из самых востребованных его применений — использование в качестве условия отбора: вместо того чтобы перечислять значения вручную или передавать их параметром-массивом, список значений формируется другим запросом прямо в тексте основного.

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

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

Как устроено условие с вложенным запросом

Условие с подзапросом строится по шаблону: ГДЕ Поле В (ВЫБРАТЬ Поле ИЗ Таблица ГДЕ ...). Оператор В проверяет, входит ли значение поля в набор, который вернул подзапрос. Оператор НЕ В делает обратное — отбирает записи, которых в наборе нет.

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

Подзапрос может содержать собственные секции: ГДЕ, СГРУППИРОВАТЬ ПО, ИМЕЮЩИЕ, соединения и даже собственные вложенные запросы. Платформа допускает вложенность, но на практике глубже двух-трёх уровней текст становится трудно читаемым, и его лучше перестроить на временных таблицах.

Базовый пример: отбор по списку из подзапроса

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

ВЫБРАТЬ

Номенклатура.Ссылка КАК Товар,

Номенклатура.Наименование

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.Ссылка В

(ВЫБРАТЬ РАЗЛИЧНЫЕ

Продажи.Номенклатура

ИЗ

РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК Продажи)

Обратите внимание на РАЗЛИЧНЫЕ внутри подзапроса: оно убирает дубликаты и уменьшает набор, с которым сравнивается основное поле. Формально СУБД часто сама устраняет дубли при проверке В, но явное указание делает намерение понятным и иногда упрощает план выполнения.

Параметры виртуальной таблицы (&НачалоПериода, &КонецПериода) передаются как обычно — через Запрос.УстановитьПараметр() в модуле. Подзапрос имеет доступ ко всем параметрам внешнего запроса, отдельно объявлять их не нужно.

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

Отрицание: НЕ В и проблема незаполненных значений

Оператор НЕ В отбирает записи, отсутствующие в результате подзапроса. Типовой сценарий — найти контрагентов, по которым нет ни одного документа реализации за период, или сотрудников без начислений.

ВЫБРАТЬ

Контрагенты.Ссылка

ИЗ

Справочник.Контрагенты КАК Контрагенты

ГДЕ

НЕ Контрагенты.Ссылка В

(ВЫБРАТЬ РАЗЛИЧНЫЕ

Реализация.Контрагент

ИЗ

Документ.РеализацияТоваровУслуг КАК Реализация

ГДЕ

Реализация.Проведен

И Реализация.Дата МЕЖДУ &Начало И &Конец)

Здесь кроется известная ловушка. Если подзапрос возвращает значение NULL (например, поле реквизита не заполнено в части документов), сравнение с NULL даёт не ИСТИНА или ЛОЖЬ, а неопределённость — и строка выпадает из результата. Итог: запрос «молча» теряет часть данных, и ошибку трудно заметить.

Защита простая: в подзапросе отсекать NULL условием ГДЕ НЕ Реализация.Контрагент ЕСТЬ NULL или использовать ЕСТЬNULL. Это особенно важно для НЕ В, где поведение NULL наименее интуитивно.

Подзапрос в условии В ИЕРАРХИИ и составные условия

Условие В ИЕРАРХИИ с подзапросом позволяет отобрать элементы справочника, входящие в любую из групп, которые вернул подзапрос. Например — товары из всех групп, у которых заполнен определённый признак:

ГДЕ

Номенклатура.Ссылка В ИЕРАРХИИ

(ВЫБРАТЬ

Группы.Ссылка

ИЗ

Справочник.Номенклатура КАК Группы

ГДЕ

Группы.ЭтоГруппа

И Группы.ПометкаУдаления = ЛОЖЬ)

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

Отдельный частный случай — сравнение результата агрегатного подзапроса с параметром. Условие вида ГДЕ &Порог < (ВЫБРАТЬ СУММА(...) ...) работает, если подзапрос возвращает одну строку с одним полем. Если строк несколько, платформа выдаст ошибку выполнения.

Производительность: когда подзапрос тормозит

Сам по себе вложенный запрос — не проблема: современные СУБД преобразуют условие В с подзапросом в полусоединение и выполняют его эффективно. Тормоза начинаются там, где подзапрос обращается к большой таблице без индексированного отбора или где вложенность накапливается.

Типичные признаки неоптимального запроса: подзапрос читает полную таблицу документов без отбора по периоду; в подзапросе соединяются несколько виртуальных таблиц; одно и то же условие-подзапрос повторяется в тексте два-три раза (оно будет вычисляться каждый раз заново).

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

Контрольные точки

Как проверить результат

Количество строк результата
Норма

Совпадает с контрольным расчётом по тем же данным вручную или через отчёт

Тревожный признак

Строк меньше ожидаемого — проверьте NULL в подзапросе и условия отбора

Время выполнения
Норма

Запрос отрабатывает за доли секунды или единицы секунд на рабочем объёме

Тревожный признак

Десятки секунд — смотрите план запроса и индексы по полям отбора

Поведение при пустом подзапросе
Норма

В возвращает пустой результат, НЕ В — все записи, без ошибок

Тревожный признак

Ошибка выполнения или неожиданно полный/пустой результат

Альтернатива: временные таблицы вместо вложенного запроса

Когда логика условия усложняется — нужны группировки, несколько шагов отбора, повторное использование промежуточного списка — подзапрос стоит заменить временной таблицей. Менеджер временных таблиц позволяет выполнить подготовительный запрос один раз, поместить результат в таблицу и использовать её в условии В или в соединении.

ВЫБРАТЬ РАЗЛИЧНЫЕ

Продажи.Номенклатура КАК Товар

ПОМЕСТИТЬ ПроданныеТовары

ИЗ

РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , ) КАК Продажи

;

ВЫБРАТЬ

Номенклатура.Ссылка

ИЗ

Справочник.Номенклатура КАК Номенклатура

ГДЕ

Номенклатура.Ссылка В (ВЫБРАТЬ Товар ИЗ ПроданныеТовары)

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

Обратная сторона — временная таблица материализуется в памяти или на диске сервера. Для очень больших промежуточных наборов это расход ресурсов, поэтому в ПОМЕСТИТЬ стоит отбирать только нужные поля и минимально необходимые строки.

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

Вложенный запрос в условии В

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

Временная таблица

Подходит для многошаговой логики, повторного использования списка и отладки. Можно индексировать. Требует МенеджерВременныхТаблиц и аккуратности с объёмом данных.

Левое соединение с проверкой ЕСТЬ NULL

Замена для НЕ В: соединяем с подзапросом и отбираем строки, где правая часть NULL. Часто даёт более предсказуемый план на больших таблицах и не страдает от проблемы NULL.

Типичные ошибки и как их избежать

Первая ошибка — подзапрос возвращает несколько полей. Синтаксическая проверка это отловит, но при генерации текста запроса из кода (конкатенацией строк) ошибка всплывёт только в момент выполнения. Правило: в условии В подзапрос всегда содержит одно поле в списке выборки.

Вторая ошибка — несовпадение типов. Например, сравнение ссылки на справочник с полем составного типа, куда входят и документы. Запрос выполнится, но отбор может не сработать так, как ожидается, или план запроса деградирует из-за неявных преобразований. Проверяйте типы полей в конструкторе запроса.

Третья — игнорирование NULL в НЕ В, описанная выше. Четвёртая — копирование одного и того же подзапроса в несколько условий одного запроса: это и дублирование кода, и лишние вычисления. Вынесите такой набор во временную таблицу.

Типичные ошибки
01
Несколько полей в списке выборки подзапроса условия В — ошибка проверки или выполнения текста запроса
02
NULL в результате подзапроса при НЕ В — строки молча пропадают из результата; добавляйте НЕ ... ЕСТЬ NULL
03
Подзапрос без отбора по периоду по полной таблице документов — резкое падение производительности
04
Дублирование одного подзапроса в нескольких условиях — лишние вычисления; выносите во временную таблицу
05
Сравнение скалярного подзапроса без агрегата или ПЕРВЫЕ 1 — ошибка, если строк окажется больше одной

Порядок отладки запроса с условием-подзапросом

Отлаживать такой запрос удобнее всего по частям. Сначала выполните подзапрос отдельно в консоли запросов и убедитесь, что он возвращает ожидаемый список значений и что среди них нет NULL. Затем выполните основной запрос с жёстко заданным списком значений (через В (&Список)) и сверьте результат.

После этого соберите полный текст и сравните количество строк с контрольным вариантом. Расхождение почти всегда означает одно из трёх: NULL в подзапросе, лишнее условие внутри подзапроса, отсекающее нужные значения, или ошибку в логике И/ИЛИ снаружи.

Порядок отладки
  1. 1
    Выполните подзапрос отдельно, проверьте список значений и отсутствие NULL
  2. 2
    Подставьте в основной запрос фиксированный список через параметр и зафиксируйте контрольный результат
  3. 3
    Соберите полный запрос с вложенным условием и сравните количество строк с контрольным
  4. 4
    При расхождении проверьте условия внутри подзапроса, NULL и приоритет И/ИЛИ
  5. 5
    При медленной работе снимите план запроса и проверьте индексы по полям отбора подзапроса
Итог

Что важно запомнить

Вложенный запрос как условие записывается через В / НЕ В / В ИЕРАРХИИ и должен возвращать одно поле совместимого типа. Главные риски — NULL при НЕ В и отсутствие отбора по периоду внутри подзапроса. При усложнении логики переходите на временные таблицы: их проще отлаживать и индексировать.

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

Можно ли использовать вложенный запрос в условии соединения?

Да. Подзапрос допустим как источник в левом или правом соединении: ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ...) КАК Псевдоним ПО условие. Это стандартный приём для предварительной группировки данных перед соединением с основной таблицей.

Чем В с подзапросом отличается от передачи массива в параметр?

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

Почему запрос с НЕ В вернул пустой результат, хотя данные есть?

Наиболее вероятная причина — подзапрос возвращает хотя бы одно значение NULL. Сравнение с NULL делает условие неопределённым для всех строк. Добавьте в подзапрос условие НЕ Поле ЕСТЬ NULL и повторите выполнение.

Есть ли ограничение на глубину вложенности подзапросов?

Жёсткого документированного предела, с которым сталкиваются на практике, платформа не накладывает, но глубокая вложенность ухудшает читаемость и может ухудшать план выполнения. Больше двух-трёх уровней — повод перестроить запрос на временных таблицах.

Работает ли условие с подзапросом в СКД?

Да, в наборе данных типа «Запрос» можно использовать любые допустимые конструкции языка запросов, включая подзапросы в условиях. Дополнительно СКД позволяет выносить отборы в настройки отчёта, что часто избавляет от необходимости жёстко зашивать условие в текст.

Как проверить, что подзапрос возвращает одно значение для скалярного сравнения?

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