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

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

Как работает вложенный запрос в 1С

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

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

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

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

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

Вложенный запрос записывается в круглых скобках в предложении ИЗ вместо имени таблицы. Внутри скобок находится полноценный запрос со своими полями, источниками, отборами и группировками. Результату обязательно присваивается псевдоним, через который внешний запрос обращается к его полям.

ВЫБРАТЬ

ВложенныйЗапрос.Номенклатура,

ВложенныйЗапрос.Количество

ИЗ

(ВЫБРАТЬ

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

СУММА(Продажи.Количество) КАК Количество

ИЗ

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

СГРУППИРОВАТЬ ПО

Продажи.Номенклатура) КАК ВложенныйЗапрос

ГДЕ

ВложенныйЗапрос.Количество > 100

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

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

Как платформа и СУБД выполняют вложенный запрос

Консоль запросов и конструктор показывают только текст на языке запросов 1С, но реальную работу выполняет СУБД. Платформа транслирует текст запроса в SQL (для клиент-серверного варианта) и передаёт его серверу баз данных. Вложенный запрос при этом превращается в производную таблицу (derived table) внутри SQL-запроса.

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

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

Типичные сценарии применения

Отбор по результату группировки

Самый распространённый случай: нужно сначала сгруппировать данные и вычислить агрегаты, а затем отобрать строки по значению агрегата. В языке запросов 1С есть предложение ИМЕЮЩИЕ, которое решает эту задачу без вложенного запроса, но оно работает только с полями группировки и агрегатами. Если после отбора нужно дотянуть дополнительные поля или соединить итог с другими таблицами — без вложенного запроса или временной таблицы не обойтись.

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

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

Получение «последних» значений через максимум периода

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

Вложенный запрос против временной таблицы

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

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

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

Что выбрать

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

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

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

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

Виртуальная таблица регистра

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

Вложенные запросы в условиях: конструкция В

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

ВЫБРАТЬ

Реализация.Ссылка

ИЗ

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

ГДЕ

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

(ВЫБРАТЬ

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

ИЗ

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

ГДЕ

Контрагенты.Регион = &Регион)

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

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

Проверка и оптимизация

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

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

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

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

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

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

Во вложенном запросе нельзя использовать предложения ИТОГИ — итоги допустимы только на верхнем уровне запроса. Также вложенный запрос не может обращаться к временным таблицам, созданным позже по тексту пакета: порядок выполнения запросов пакета строго последовательный.

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

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

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

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

Стабильно укладывается в приемлемое для операции время на реальном объёме

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

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

План запроса на СУБД
Норма

Используются индексы, соединения по узким наборам

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

Полные сканирования больших таблиц, вложенные циклы с миллионами итераций

Результат запроса
Норма

Количество строк и значения агрегатов совпадают с контрольным расчётом

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

Дубли строк, потерянные записи, неожиданные NULL

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

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

Да. В конструкторе на закладке «Таблицы и поля» есть возможность добавить вложенный запрос как источник — он отображается отдельным элементом дерева. Для сложных конструкций удобнее редактировать текст вручную, так как конструктор не всегда наглядно показывает многоуровневую вложенность.

Почему запрос с вложенным запросом работает быстро в тестовой базе, но медленно в рабочей?

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

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

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

Сколько уровней вложенности допустимо?

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

Может ли вложенный запрос обращаться к временной таблице?

Да, если временная таблица создана раньше в том же пакете запросов и в том же менеджере временных таблиц. Вложенный запрос в предложении ИЗ может ссылаться на неё как на обычный источник. Обратиться к временной таблице, созданной позже по тексту пакета, нельзя.

Почему условие НЕ В с подзапросом не возвращает записи?

Почти всегда причина в значениях NULL в результате подзапроса. Из-за трёхзначной логики SQL сравнение с NULL даёт неопределённость, и условие отбрасывает все строки. Добавьте в подзапрос отбор «поле НЕ ЕСТЬ NULL» или замените конструкцию на соединение с проверкой на NULL.

Итог

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

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