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