Вложенные запросы в 1С:Предприятие — это мощный инструмент, который позволяет решать задачи, недоступные стандартными средствами языка запросов. Они помогают извлекать данные с несколькими уровнями вложенности, фильтровать результаты на основе сложных условий и оптимизировать производительность при работе с большими объемами информации. Однако многие разработчики избегают их использования, считая синтаксис слишком сложным или опасаясь снижения быстродействия. На практике же грамотно построенные вложенные запросы часто становятся единственным эффективным решением для нетривиальных задач.
В этой статье мы разберём, зачем нужны вложенные запросы в 1С, когда их применение оправдано, а когда лучше выбрать альтернативные подходы. Вы узнаете о типичных сценариях использования — от простых фильтров до сложных аналитических отчётов, — а также о подводных камнях, которые могут поджидать новичков. Особое внимание уделим вопросам производительности: как правильно структурировать запрос, чтобы он работал быстро даже на больших базах данных.
Что такое вложенный запрос и как он устроен
Вложенный запрос (или подзапрос) — это конструкция языка запросов 1С, которая позволяет использовать результат одного запроса как условие или источник данных для другого. Внешне он выглядит как запрос внутри запроса, заключённый в круглые скобки. Например:
ВЫБРАТЬ
Товары.Наименование,
Товары.Артикул
ИЗ
Справочник.Товары КАК Товары
ГДЕ
Товары.Артикул В (
ВЫБРАТЬ
ДокументПоступление.Товары.Артикул
ИЗ
Документ.ПоступлениеТоваров КАК ДокументПоступление
ГДЕ
ДокументПоступление.Дата >= &НАЧАЛОДНЯ()
)
В этом примере внутренний запрос возвращает список артикулов товаров, поступивших сегодня, а внешний — выбирает из справочника только те товары, артикулы которых попали в этот список. Такая структура позволяет динамически формировать условия отбора без предварительной подготовки данных.
Важно понимать, что вложенный запрос выполняется для каждой строки внешнего запроса (если используется в условии ГДЕ), что может влиять на производительность. Однако в 1С:Предприятие 8.3 оптимизатор запросов часто преобразует такие конструкции в более эффективные планы выполнения.
Основные сценарии применения вложенных запросов
Вложенные запросы незаменимы в ситуациях, когда требуется:
- 🔍 Фильтрация по динамическим условиям — когда список значений для отбора формируется на лету (например, "показать клиентов, которые заказывали товары из текущей группы").
- 📊 Агрегация данных с группировкой — когда нужно посчитать суммы, количества или средние значения с учётом вложенных условий (например, "показать категории товаров, средняя цена которых выше средней по всем категориям").
- 🔄 Работа с иерархическими данными — когда требуется обойти дерево справочников или анализировать связи "один-ко-многим" (например, "найти всех подчинённых сотрудника, у которого есть подчинённые с окладом выше 100 000").
- 🔗 Сравнение данных из разных источников — когда нужно сопоставить записи из несвязанных таблиц (например, "показать товары, которые есть в прайс-листе, но отсутствуют в остатках").
Рассмотрим конкретный пример: необходимо вывести список контрагентов, которые сделали заказы на сумму больше средней по всем заказам. Без вложенного запроса пришлось бы сначала вычислять среднюю сумму отдельно, а затем использовать её во втором запросе. С вложенным запросом это делается в одну операцию:
ВЫБРАТЬ
Заказы.Контрагент КАК Контрагент,
СУММА(Заказы.СуммаДокумента) КАК ОбщаяСумма
ИЗ
Документ.ЗаказПокупателя КАК Заказы
ГДЕ
Заказы.Контрагент В (
ВЫБРАТЬ
ЗаказыВложенный.Контрагент
ИЗ
Документ.ЗаказПокупателя КАК ЗаказыВложенный
ГРУППИРОВАТЬ ПО
ЗаказыВложенный.Контрагент
ИМЕЮЩИЕ
СУММА(ЗаказыВложенный.СуммаДокумента) > (
ВЫБРАТЬ
СРЕДНЕЕ(СуммаЗаказа)
ИЗ
(
ВЫБРАТЬ
СУММА(ЗаказыДляСреднего.СуммаДокумента) КАК СуммаЗаказа
ИЗ
Документ.ЗаказПокупателя КАК ЗаказыДляСреднего
ГРУППИРОВАТЬ ПО
ЗаказыДляСреднего.Контрагент
) КАК СредниеЗначения
)
)
ГРУППИРОВАТЬ ПО
Заказы.Контрагент
Этот запрос демонстрирует многоуровневую вложенность, где внутренний подзапрос вычисляет среднюю сумму заказов по всем контрагентам, а внешний — фильтрует контрагентов, чьи заказы превышают это значение.
Если вложенный запрос возвращает одно значение (например, среднюю сумму), его можно использовать напрямую в условии с оператором сравнения: ГДЕ СУММА(Заказы.Сумма) > (ВЫБРАТЬ СРЕДНЕЕ(Сумма) ИЗ Заказы).
Вложенные запросы vs временные таблицы: что выбрать
Часто вместо вложенных запросов разработчики используют временные таблицы (ВТ). Этот подход тоже имеет право на жизнь, но у каждого метода есть свои плюсы и минусы. Давайте сравним их в ключевых аспектах:
| Критерий | Вложенные запросы | Временные таблицы |
|---|---|---|
| Читаемость кода | Может быть сложной при глубокой вложенности | Обычно проще для восприятия (данные разбиты на этапы) |
| Производительность | Зависит от оптимизатора. В 1С 8.3.20+ часто оптимизируется автоматически | Требует ручного управления индексами и структурой данных |
| Гибкость | Позволяет динамически формировать условия | Требует предварительного заполнения данными |
| Объём данных | Подходит для небольших и средних выборок | Лучше справляется с большими объёмами (можно разбить на части) |
| Сложность отладки | Трудно дебажить из-за вложенности | Проще анализировать промежуточные результаты |
Когда стоит выбрать вложенные запросы:
- ✅ Нужно однократное выполнение без сохранения промежуточных данных.
- ✅ Условия отбора динамически меняются в зависимости от контекста.
- ✅ Запрос простой или средней сложности (не более 2-3 уровней вложенности).
Когда лучше использовать временные таблицы:
- ✅ Работа с большими объёмами данных (тысячи строк).
- ✅ Нужно многократно обращаться к одним и тем же промежуточным результатам.
- ✅ Требуется сложная постобработка данных после выборки.
Пример оптимизации с временными таблицами
Вместо вложенного запроса для расчёта средней цены по категориям можно сначала заполнить ВТ средними значениями, а затем присоединить её к основному запросу. Это ускорит выполнение, если категорий много, а товаров в них тысячи.
Типичные ошибки при работе с вложенными запросами
Даже опытные разработчики иногда допускают ошибки, которые ведут к медленной работе или некорректным результатам. Вот наиболее распространённые из них:
- Избыточная вложенность. Запросы с 4-5 уровнями вложенности не только сложны для чтения, но и часто выполняются крайне долго. Если вам кажется, что запрос стал похож на "матрешку", пора разбивать его на части или использовать временные таблицы.
- Неправильное использование
ВиНЕ В. ОператорНЕ Вможет приводить к полному сканированию таблиц, если подзапрос возвращает много строк. В таких случаях лучше использоватьЛЕВОЕ СОЕДИНЕНИЕс условиемГДЕ ... ЕСТЬ NULL. - Игнорирование индексов. Вложенные запросы часто выполняются медленно, если поля, используемые в условиях, не проиндексированы. Проверьте план выполнения запроса в консоли запросов (
ПКА). - Коррелированные подзапросы без необходимости. Если внутренний запрос ссылается на поля внешнего (например,
ГДЕ Существует (ВЫБРАТЬ ... ГДЕ ВнешняяТаблица.Поле = ВнутренняяТаблица.Поле)), это может значительно замедлить выполнение.
⚠️ Внимание: В версиях 1С:Предприятие ниже 8.3.10 оптимизатор запросов работал менее эффективно. Если вы поддерживаете устаревшие конфигурации, тестируйте производительность вложенных запросов особенно тщательно.
Рассмотрим пример неэффективного запроса с коррелированным подзапросом:
ВЫБРАТЬ
Товары.Наименование,
Товары.Артикул
ИЗ
Справочник.Товары КАК Товары
ГДЕ
СУЩЕСТВУЕТ (
ВЫБРАТЬ
РАЗРЕШЕННЫЕ
ИЗ
РегистрСведений.ЦеныТоваров КАК Цены
ГДЕ
Цены.Товар = Товары.Ссылка
И Цены.Цена > 1000
)
Здесь для каждого товара внутренний запрос проверяет наличие записей в регистре цен. Если товаров 10 000, то подзапрос выполнится 10 000 раз! Гораздо эффективнее переписать его с использованием соединения:
ВЫБРАТЬ РАЗЛИЧНЫЕ
Товары.Наименование,
Товары.Артикул
ИЗ
Справочник.Товары КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныТоваров КАК Цены
ПО Товары.Ссылка = Цены.Товар
ГДЕ
Цены.Цена > 1000
Коррелированные подзапросы — главный враг производительности. Всегда пытайтесь заменить их соединениями или временными таблицами.
Оптимизация вложенных запросов: практические советы
Чтобы вложенные запросы работали быстро, следуйте этим рекомендациям:
- Ограничивайте глубину вложенности. Старайтесь не использовать более 2-3 уровней. Если логика требует большей вложенности, разбейте запрос на части с временными таблицами.
- Используйте
СУЩЕСТВУЕТвместоВдля проверки наличия. КонструкцияСУЩЕСТВУЕТ (ВЫБРАТЬ РАЗРЕШЕННЫЕ ...)обычно выполняется быстрее, чем... В (ВЫБРАТЬ ...), так как останавливается при нахождении первого совпадения. - Переносите условия во внешние запросы. Если внутренний запрос возвращает много строк, но внешнему нужна только часть, добавьте фильтры во внутренний запрос.
- Избегайте
НЕ Вдля больших наборов данных. Замените наЛЕВОЕ СОЕДИНЕНИЕ ... ГДЕ ЕСТЬ NULL. - Анализируйте план выполнения. В консоли запросов (
ПКА) смотрите, как оптимизатор преобразует ваш запрос. Иногда добавление явных соединений вместо вложенных подзапросов даёт выигрыш в скорости.
Пример оптимизированного запроса для поиска товаров, которые не продавались в текущем месяце:
ВЫБРАТЬ
Товары.Наименование,
Товары.Артикул
ИЗ
Справочник.Товары КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваров.Товары КАК Продажи
ПО Товары.Ссылка = Продажи.Товар
И Продажи.Документ.Дата МЕЖДУ &НАЧАЛОМЕСЯЦА(&ТЕКУЩАЯДАТА()) И &КОНЕЦМЕСЯЦА(&ТЕКУЩАЯДАТА())
ГДЕ
Продажи.Товар ЕСТЬ NULL
Этот запрос эффективнее, чем вариант с НЕ В, потому что:
- 📉 Не сканирует всю таблицу продаж для каждого товара.
- 🔍 Использует индексы по полям
ТовариДата. - ⚡ Останавливается при нахождении первого совпадения (или его отсутствии).
Анализ плана выполнения (ПКА)|Проверка наличия индексов на ключевых полях|Замена НЕ В на ЛЕВОЕ СОЕДИНЕНИЕ|Разбиение на временные таблицы при глубокой вложенности|Тестирование на реальных данных-->
Когда вложенные запросы — единственное решение
Несмотря на альтернативы, есть задачи, где вложенные запросы незаменимы. Рассмотрим такие случаи:
- Динамическая фильтрация по подчинённым данным. Например, нужно показать документы, у которых есть хотя бы одна строка с определённым свойством, но само свойство вычисляется сложным образом. Временные таблицы здесь не помогут, так как условие формируется "на лету".
- Работа с рекурсивными данными. Если требуется обойти иерархию справочника (например, найти всех подчинённых сотрудника на любом уровне вложенности), вложенные запросы с рекурсией (
ССЫЛКА.Родитель В (...)) могут быть единственным вариантом. - Сравнение агрегированных данных. Например, "показать товары, у которых средняя цена продажи выше средней цены по их категории". Здесь без вложенных запросов не обойтись, так как нужно сначала посчитать средние значения по группам.
- Проверка существования связанных записей с дополнительными условиями. Например, "показать клиентов, у которых есть неоплаченные заказы старше 30 дней". Временные таблицы потребуют предварительного заполнения, а вложенный запрос решит задачу в одном проходе.
Пример с рекурсивным обходом иерархии справочника "Номенклатура":
ВЫБРАТЬ
Номенклатура.Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка В (
ВЫБРАТЬ
ПодчиненнаяНоменклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК ПодчиненнаяНоменклатура
ГДЕ
ПодчиненнаяНоменклатура.Родитель В (
ВЫБРАТЬ
РодительскаяНоменклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК РодительскаяНоменклатура
ГДЕ
РодительскаяНоменклатура.Наименование = "Электроника"
)
)
Этот запрос находит все элементы справочника, которые находятся на любом уровне вложенности под группой "Электроника". Альтернативные решения (например, с использованием рекурсивных вызовов в коде) будут менее эффективны.
⚠️ Внимание: В 1С:Предприятие 8.3.18+ появилась поддержка рекурсивных запросов с конструкцией СОЕДИНИТЬ РЕКУРСИВНО. В некоторых случаях это может быть более читаемой альтернативой глубоко вложенным запросам.
Альтернативы вложенным запросам: когда и что использовать
Вложенные запросы — не всегда лучший выбор. В зависимости от задачи можно рассмотреть следующие альтернативы:
| Задача | Альтернатива | Когда применять |
|---|---|---|
| Фильтрация по списку значений | Временная таблица с предварительным заполнением | Если список значений большой или используется многократно |
| Проверка существования связанных записей | ЛЕВОЕ СОЕДИНЕНИЕ ... ГДЕ ЕСТЬ NULL |
Для улучшения производительности, особенно в старых версиях 1С |
| Агрегация данных с группировкой | Подзапросы в секции ИМЕЮЩИЕ |
Если нужно отфильтровать группы по агрегированным значениям |
| Работа с иерархическими данными | Рекурсивные функции в коде (на клиенте или сервере) | Если глубина вложенности велика и запрос становится слишком сложным |
| Сложные условия с несколькими уровнями | Объединение запросов через ОБЪЕДИНИТЬ |
Если логика разбивается на независимые части |
Пример замены вложенного запроса на соединение для проверки наличия продаж:
// Вместо:
ВЫБРАТЬ
Товары.Наименование
ИЗ
Справочник.Товары КАК Товары
ГДЕ
СУЩЕСТВУЕТ (
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ИЗ Документ.Продажи.Товары КАК Продажи
ГДЕ Продажи.Товар = Товары.Ссылка
)
// Используем:
ВЫБРАТЬ РАЗЛИЧНЫЕ
Товары.Наименование
ИЗ
Справочник.Товары КАК Товары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Продажи.Товары КАК Продажи
ПО Товары.Ссылка = Продажи.Товар
Второй вариант не только проще для чтения, но и часто выполняется быстрее за счёт использования индексов.
FAQ: Ответы на частые вопросы о вложенных запросах в 1С
Можно ли использовать вложенные запросы в отчётах на СКД?
Да, но с оговорками. В Системе компоновки данных (СКД) вложенные запросы можно использовать в настройках отбора или в вычисляемых полях, однако это может усложнить отладку. Для сложных условий лучше подготовить данные заранее во временной таблице и уже её использовать как источник для СКД.
Как узнать, почему вложенный запрос работает медленно?
Используйте План запроса в консоли запросов (ПКА). Обратите внимание на операции ПОЛНОЕ СКАНИРОВАНИЕ (table scan) — они указывают на отсутствие индексов. Также проверьте, не превратил ли оптимизатор ваш запрос в коррелированный подзапрос (это видно в плане как Apply).
Можно ли во вложенном запросе использовать параметры?
Да, параметры (&Параметр) работают во вложенных запросах так же, как и во внешних. Например:
ВЫБРАТЬ Товары.Наименование
ИЗ Справочник.Товары КАК Товары
ГДЕ Товары.Артикул В (
ВЫБРАТЬ Артикулы.Значение
ИЗ РегистрСведений.АртикулыПоставщиков КАК Артикулы
ГДЕ Артикулы.Поставщик = &Поставщик
)
Что делать, если вложенный запрос возвращает ошибку "Слишком сложный запрос"?
Эта ошибка возникает, когда оптимизатор не может построить план выполнения (обычно при глубокой вложенности или сложных условиях). Решения:
- Разбейте запрос на части с использованием временных таблиц.
- Упростите условия (например, замените
НЕ ВнаЛЕВОЕ СОЕДИНЕНИЕ). - Обновите платформу — в новых версиях лимит сложности выше.
Как во вложенном запросе получить данные из другой базы?
Прямо во вложенном запросе обратиться к другой базе нельзя. Варианты:
- Использовать
HTTPСервисилиWS-соединениедля получения данных до выполнения запроса. - Создать временную таблицу с данными из другой базы (например, через
ЗагрузкаДанных). - Использовать распределённые базы данных (если конфигурация это поддерживает).