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

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

Вложенный запрос в условии ГДЕ в 1С: как писать и когда заменить

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

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

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

Материал разбирает синтаксис операторов В и В ИЕРАРХИИ с вложенным запросом, рабочие примеры кода, ограничения платформы, типичные ошибки и альтернативы через соединение таблиц и временные таблицы.

Синтаксис вложенного запроса в условии ГДЕ

Базовая форма выглядит так: в секции ГДЕ указывается поле, оператор В и в скобках — вложенный запрос, начинающийся с ключевого слова ВЫБРАТЬ. Вложенный запрос обязан возвращать ровно одно поле, тип которого совместим с проверяемым значением.

ВЫБРАТЬ

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

ИЗ

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

ГДЕ

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

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

Продажи.Контрагент

ИЗ

РегистрНакопления.Продажи КАК Продажи

ГДЕ

Продажи.Период >= &НачалоПериода)

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

Оператор В ИЕРАРХИИ с подзапросом

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

ВЫБРАТЬ

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

ИЗ

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

ГДЕ

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

(ВЫБРАТЬ

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

ИЗ

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

ГДЕ

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

И Группы.Наименование ПОДОБНО "%Акция%")

Этот пример отбирает товары из всех групп, в названии которых встречается слово «Акция». Самостоятельно перебирать уровни иерархии соединениями пришлось бы через многократные соединения таблицы самой с собой, что громоздко и медленно. Оператор В ИЕРАРХИИ делегирует эту работу платформе.

Типичные сценарии использования

Фильтрация по данным другой таблицы

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

Исключение записей через НЕ В

Обратная задача — найти «осиротевшие» данные: товары без движений, контрагенты без документов. Здесь подзапрос формирует список использованных ссылок, а внешний запрос отбирает всё остальное. Перед применением на больших справочниках проверьте план запроса — часто левое соединение с условием ЕСТЬ NULL выполняется заметно быстрее.

Фильтр по агрегированному условию

Подзапрос может содержать группировку и ИМЕЮЩИЕ. Например, выбрать контрагентов, у которых сумма продаж за период превысила порог: подзапрос группирует продажи по контрагенту с условием ИМЕЮЩИЕ СУММА(Сумма) > &Порог, а внешний запрос отбирает этих контрагентов через В.

Связанная таблица — только фильтрПодзапрос уместен

Поля второй таблицы не нужны в результате, нужна только проверка вхождения

Нужна иерархия справочникаИспользуйте В ИЕРАРХИИ

Ручной обход уровней иерархии сложнее и медленнее

Обе таблицы большиеПроверьте альтернативы

Сравните скорость с соединением и временной таблицей

Список значений известен заранееПодзапрос не нужен

Передайте массив или список через параметр: ГДЕ Поле В (&Список)

Ограничения и подводные камни

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

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

Третий риск — NULL в результате подзапроса. Сравнение с NULL в трёхзначной логике даёт не ИСТИНА и не ЛОЖЬ, а неопределённость, и строки могут теряться из результата. Добавляйте в подзапрос условие Поле НЕ ЕСТЬ NULL, если такая ситуация возможна.

Альтернативы: соединение и временные таблицы

Левое соединение с проверкой ЕСТЬ NULL — классическая замена для НЕ В. Внешняя таблица соединяется с фильтрующей, и отбираются строки, где связанная ссылка равна NULL. На больших данных с подходящими индексами это часто быстрее, чем НЕ В с подзапросом.

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

Если список значений для фильтра можно получить заранее в коде, проще всего передать его параметром: ГДЕ Поле В (&СписокСсылок), где параметр — массив или список значений. Это самый быстрый и прозрачный вариант, но он не подходит, когда список должен вычисляться на момент выполнения запроса.

Сравнение подходов

Вложенный запрос в ГДЕ

Компактно и читаемо; риск медленного выполнения на больших таблицах, проблемы с NULL

Левое соединение + ЕСТЬ NULL

Быстрее при наличии индексов; текст запроса громоздче, поля второй таблицы попадают в контекст

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

Предсказуемая скорость, удобная отладка; требует пакетного запроса и больше кода

Параметр со списком

Максимально просто и быстро; список нужно сформировать заранее в коде

Порядок написания и проверки запроса

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

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

Порядок действий
  1. 1
    Напишите и отладьте подзапрос отдельно: одно поле, без NULL, корректный набор значений
  2. 2
    Встройте подзапрос в условие ГДЕ через В или В ИЕРАРХИИ
  3. 3
    Проверьте результат на данных с известным итогом
  4. 4
    Замерьте время выполнения на реальном объёме
  5. 5
    При медленной работе перепишите через соединение или временную таблицу и сравните

Типичные ошибки

Ошибка номер один — подзапрос возвращает несколько полей. Запрос не выполнится, и сообщение об ошибке не всегда сразу указывает на причину. Вторая частая ошибка — забытый РАЗЛИЧНЫЕ в подзапросе: дубликаты значений раздувают промежуточный результат, хотя на итоговую логику не влияют.

Третья группа ошибок — логические: сравнение полей несовместимых типов (например, ссылки разных справочников), игнорирование NULL, использование В ИЕРАРХИИ для неиерархического объекта. Ещё одна проблема — вложенные запросы в несколько уровней: формально платформа это допускает, но читаемость и скорость резко падают, и такой код почти всегда стоит переписать через временные таблицы.

Типичные ошибки
01
Подзапрос возвращает несколько полей — запрос падает с ошибкой
02
Нет РАЗЛИЧНЫЕ — лишние дубликаты в промежуточной выборке и потеря скорости
03
Игнорирование NULL в подзапросе — строки неожиданно пропадают из результата
04
В ИЕРАРХИИ применяют к неиерархическому объекту — ошибка выполнения
05
Три-четыре уровня вложенности — запрос нечитаем и медленен, нужна декомпозиция на временные таблицы
Итог

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

Вложенный запрос в ГДЕ пишется через В (подзапрос) или В ИЕРАРХИИ (подзапрос), подзапрос возвращает одно поле. Отлаживайте подзапрос отдельно, фильтруйте NULL, а на больших таблицах сравнивайте скорость с соединением и временными таблицами.

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

Можно ли использовать параметры внутри вложенного запроса?

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

Чем В отличается от В ИЕРАРХИИ?

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

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

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

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

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

Что быстрее: вложенный запрос или соединение?

Однозначного ответа нет — зависит от СУБД, объёма таблиц и индексов. На небольших выборках разница незаметна. На больших таблицах соединение с подходящим индексом или временная таблица нередко выигрывают. Правильный способ — замерить оба варианта на ваших данных.

Можно ли передать в подзапрос таблицу значений из кода?

Напрямую в подзапрос — нет, но таблицу значений можно передать параметром и материализовать во временную таблицу через конструкцию ВЫБРАТЬ ... ПОМЕСТИТЬ с источником-параметром. Дальше временная таблица используется в условии В или в соединении как обычно.