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