Разработка и Конфигурация · 12 мин
Вложенный запрос как условие в 1С: синтаксис, примеры и ошибки
Как использовать вложенный запрос в условии В, НЕ В и В ИЕРАРХИИ в запросах 1С: синтаксис, примеры с подзапросами, типичные ошибки и проверка результата.
Вложенный запрос (подзапрос) в языке запросов 1С — это запрос, заключённый в круглые скобки внутри другого запроса. Одно из самых востребованных его применений — использование в качестве условия отбора: вместо того чтобы перечислять значения вручную или передавать их параметром-массивом, список значений формируется другим запросом прямо в тексте основного.
Такой приём нужен, когда условие динамическое: например, выбрать номенклатуру, по которой были продажи за период, контрагентов без документов оплаты или сотрудников, входящих в определённую группу. Подзапрос вычисляется на сервере СУБД, и весь отбор выполняется за одно обращение к базе — без промежуточной выгрузки в код.
Ниже разобраны синтаксис условий В и НЕ В с подзапросом, типовые сценарии, ограничения платформы, влияние на производительность и альтернативы — временные таблицы и соединения.
Как устроено условие с вложенным запросом
Условие с подзапросом строится по шаблону: ГДЕ Поле В (ВЫБРАТЬ Поле ИЗ Таблица ГДЕ ...). Оператор В проверяет, входит ли значение поля в набор, который вернул подзапрос. Оператор НЕ В делает обратное — отбирает записи, которых в наборе нет.
Ключевое требование: подзапрос в условии В должен возвращать ровно одно поле. Если указать в списке выборки несколько полей, конструктор запроса выдаст ошибку ещё на этапе проверки текста. Тип возвращаемого поля должен быть сравним с типом проверяемого поля — ссылка со ссылкой, строка со строкой.
Подзапрос может содержать собственные секции: ГДЕ, СГРУППИРОВАТЬ ПО, ИМЕЮЩИЕ, соединения и даже собственные вложенные запросы. Платформа допускает вложенность, но на практике глубже двух-трёх уровней текст становится трудно читаемым, и его лучше перестроить на временных таблицах.
Базовый пример: отбор по списку из подзапроса
Классическая задача — выбрать товары, по которым были движения за период. Список таких товаров формирует подзапрос по таблице оборотов регистра накопления:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар,
Номенклатура.Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
Продажи.Номенклатура
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК Продажи)
Обратите внимание на РАЗЛИЧНЫЕ внутри подзапроса: оно убирает дубликаты и уменьшает набор, с которым сравнивается основное поле. Формально СУБД часто сама устраняет дубли при проверке В, но явное указание делает намерение понятным и иногда упрощает план выполнения.
Параметры виртуальной таблицы (&НачалоПериода, &КонецПериода) передаются как обычно — через Запрос.УстановитьПараметр() в модуле. Подзапрос имеет доступ ко всем параметрам внешнего запроса, отдельно объявлять их не нужно.
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Номенклатура.Ссылка ИЗ Справочник.Номенклатура КАК Номенклатура ГДЕ Номенклатура.Ссылка В (ВЫБРАТЬ РАЗЛИЧНЫЕ Продажи.Номенклатура ИЗ РегистрНакопления.Продажи.Обороты(&Нач, &Кон, , ) КАК Продажи)";
Запрос.УстановитьПараметр("Нач", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Кон", КонецМесяца(ТекущаяДатаСеанса()));
Результат = Запрос.Выполнить().Выгрузить();Отрицание: НЕ В и проблема незаполненных значений
Оператор НЕ В отбирает записи, отсутствующие в результате подзапроса. Типовой сценарий — найти контрагентов, по которым нет ни одного документа реализации за период, или сотрудников без начислений.
ВЫБРАТЬ
Контрагенты.Ссылка
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
НЕ Контрагенты.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
Реализация.Контрагент
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Проведен
И Реализация.Дата МЕЖДУ &Начало И &Конец)
Здесь кроется известная ловушка. Если подзапрос возвращает значение NULL (например, поле реквизита не заполнено в части документов), сравнение с NULL даёт не ИСТИНА или ЛОЖЬ, а неопределённость — и строка выпадает из результата. Итог: запрос «молча» теряет часть данных, и ошибку трудно заметить.
Защита простая: в подзапросе отсекать NULL условием ГДЕ НЕ Реализация.Контрагент ЕСТЬ NULL или использовать ЕСТЬNULL. Это особенно важно для НЕ В, где поведение NULL наименее интуитивно.
Подзапрос в условии В ИЕРАРХИИ и составные условия
Условие В ИЕРАРХИИ с подзапросом позволяет отобрать элементы справочника, входящие в любую из групп, которые вернул подзапрос. Например — товары из всех групп, у которых заполнен определённый признак:
ГДЕ
Номенклатура.Ссылка В ИЕРАРХИИ
(ВЫБРАТЬ
Группы.Ссылка
ИЗ
Справочник.Номенклатура КАК Группы
ГДЕ
Группы.ЭтоГруппа
И Группы.ПометкаУдаления = ЛОЖЬ)
Подзапрос как условие свободно комбинируется с другими условиями через И, ИЛИ и скобки. Например: «товар в списке проданных И остаток меньше минимального». При комбинировании следите за приоритетом операторов — ИЛИ без скобок может расширить выборку сильнее, чем задумано.
Отдельный частный случай — сравнение результата агрегатного подзапроса с параметром. Условие вида ГДЕ &Порог < (ВЫБРАТЬ СУММА(...) ...) работает, если подзапрос возвращает одну строку с одним полем. Если строк несколько, платформа выдаст ошибку выполнения.
Производительность: когда подзапрос тормозит
Сам по себе вложенный запрос — не проблема: современные СУБД преобразуют условие В с подзапросом в полусоединение и выполняют его эффективно. Тормоза начинаются там, где подзапрос обращается к большой таблице без индексированного отбора или где вложенность накапливается.
Типичные признаки неоптимального запроса: подзапрос читает полную таблицу документов без отбора по периоду; в подзапросе соединяются несколько виртуальных таблиц; одно и то же условие-подзапрос повторяется в тексте два-три раза (оно будет вычисляться каждый раз заново).
Практический критерий: если запрос выполняется дольше нескольких секунд на рабочей базе, откройте план запроса в консоли или замерьте время по частям — сначала подзапрос отдельно, потом весь запрос. Часто выясняется, что узкое место не в самом условии, а в отсутствии индекса по полю отбора внутри подзапроса.
Как проверить результат
Совпадает с контрольным расчётом по тем же данным вручную или через отчёт
Строк меньше ожидаемого — проверьте NULL в подзапросе и условия отбора
Запрос отрабатывает за доли секунды или единицы секунд на рабочем объёме
Десятки секунд — смотрите план запроса и индексы по полям отбора
В возвращает пустой результат, НЕ В — все записи, без ошибок
Ошибка выполнения или неожиданно полный/пустой результат
Альтернатива: временные таблицы вместо вложенного запроса
Когда логика условия усложняется — нужны группировки, несколько шагов отбора, повторное использование промежуточного списка — подзапрос стоит заменить временной таблицей. Менеджер временных таблиц позволяет выполнить подготовительный запрос один раз, поместить результат в таблицу и использовать её в условии В или в соединении.
ВЫБРАТЬ РАЗЛИЧНЫЕ
Продажи.Номенклатура КАК Товар
ПОМЕСТИТЬ ПроданныеТовары
ИЗ
РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , ) КАК Продажи
;
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка В (ВЫБРАТЬ Товар ИЗ ПроданныеТовары)
Выигрыш тройной: текст читается сверху вниз как последовательность шагов; временную таблицу можно проиндексировать (ИНДЕКСИРОВАТЬ ПО Товар), что ускоряет последующие соединения; промежуточный результат легко проверить отдельным запросом при отладке.
Обратная сторона — временная таблица материализуется в памяти или на диске сервера. Для очень больших промежуточных наборов это расход ресурсов, поэтому в ПОМЕСТИТЬ стоит отбирать только нужные поля и минимально необходимые строки.
Подзапрос или временная таблица
Подходит для простого одношагового отбора. Компактный текст, не нужен менеджер временных таблиц. Ограничение: сложно отлаживать и нельзя переиспользовать результат.
Подходит для многошаговой логики, повторного использования списка и отладки. Можно индексировать. Требует МенеджерВременныхТаблиц и аккуратности с объёмом данных.
Замена для НЕ В: соединяем с подзапросом и отбираем строки, где правая часть NULL. Часто даёт более предсказуемый план на больших таблицах и не страдает от проблемы NULL.
Типичные ошибки и как их избежать
Первая ошибка — подзапрос возвращает несколько полей. Синтаксическая проверка это отловит, но при генерации текста запроса из кода (конкатенацией строк) ошибка всплывёт только в момент выполнения. Правило: в условии В подзапрос всегда содержит одно поле в списке выборки.
Вторая ошибка — несовпадение типов. Например, сравнение ссылки на справочник с полем составного типа, куда входят и документы. Запрос выполнится, но отбор может не сработать так, как ожидается, или план запроса деградирует из-за неявных преобразований. Проверяйте типы полей в конструкторе запроса.
Третья — игнорирование NULL в НЕ В, описанная выше. Четвёртая — копирование одного и того же подзапроса в несколько условий одного запроса: это и дублирование кода, и лишние вычисления. Вынесите такой набор во временную таблицу.
Порядок отладки запроса с условием-подзапросом
Отлаживать такой запрос удобнее всего по частям. Сначала выполните подзапрос отдельно в консоли запросов и убедитесь, что он возвращает ожидаемый список значений и что среди них нет NULL. Затем выполните основной запрос с жёстко заданным списком значений (через В (&Список)) и сверьте результат.
После этого соберите полный текст и сравните количество строк с контрольным вариантом. Расхождение почти всегда означает одно из трёх: NULL в подзапросе, лишнее условие внутри подзапроса, отсекающее нужные значения, или ошибку в логике И/ИЛИ снаружи.
- 1Выполните подзапрос отдельно, проверьте список значений и отсутствие NULL
- 2Подставьте в основной запрос фиксированный список через параметр и зафиксируйте контрольный результат
- 3Соберите полный запрос с вложенным условием и сравните количество строк с контрольным
- 4При расхождении проверьте условия внутри подзапроса, NULL и приоритет И/ИЛИ
- 5При медленной работе снимите план запроса и проверьте индексы по полям отбора подзапроса
Что важно запомнить
Вложенный запрос как условие записывается через В / НЕ В / В ИЕРАРХИИ и должен возвращать одно поле совместимого типа. Главные риски — NULL при НЕ В и отсутствие отбора по периоду внутри подзапроса. При усложнении логики переходите на временные таблицы: их проще отлаживать и индексировать.
Частые вопросы
Можно ли использовать вложенный запрос в условии соединения?
Да. Подзапрос допустим как источник в левом или правом соединении: ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ...) КАК Псевдоним ПО условие. Это стандартный приём для предварительной группировки данных перед соединением с основной таблицей.
Чем В с подзапросом отличается от передачи массива в параметр?
Параметр-массив формируется в коде, то есть данные сначала вычитываются из базы, а потом передаются обратно. Подзапрос вычисляет список на сервере СУБД за одно обращение. Для динамических условий подзапрос обычно быстрее и проще, массив удобен, когда список уже есть в коде.
Почему запрос с НЕ В вернул пустой результат, хотя данные есть?
Наиболее вероятная причина — подзапрос возвращает хотя бы одно значение NULL. Сравнение с NULL делает условие неопределённым для всех строк. Добавьте в подзапрос условие НЕ Поле ЕСТЬ NULL и повторите выполнение.
Есть ли ограничение на глубину вложенности подзапросов?
Жёсткого документированного предела, с которым сталкиваются на практике, платформа не накладывает, но глубокая вложенность ухудшает читаемость и может ухудшать план выполнения. Больше двух-трёх уровней — повод перестроить запрос на временных таблицах.
Работает ли условие с подзапросом в СКД?
Да, в наборе данных типа «Запрос» можно использовать любые допустимые конструкции языка запросов, включая подзапросы в условиях. Дополнительно СКД позволяет выносить отборы в настройки отчёта, что часто избавляет от необходимости жёстко зашивать условие в текст.
Как проверить, что подзапрос возвращает одно значение для скалярного сравнения?
Выполните подзапрос отдельно и посмотрите число строк. Для гарантии одной строки используйте агрегатную функцию без группировки (СУММА, МАКСИМУМ, КОЛИЧЕСТВО) либо конструкцию ВЫБРАТЬ ПЕРВЫЕ 1 с сортировкой.