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

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

Вложенный запрос или временная таблица в 1С: что выбрать

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

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

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

Ниже разобраны механизмы работы обоих подходов, критерии выбора, влияние на производительность, типичные ошибки и способы проверить, что выбранный вариант действительно оптимален для конкретной задачи.

Как работают вложенный запрос и временная таблица в 1С

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

Временная таблица создаётся конструкцией ПОМЕСТИТЬ ВТ ИмяТаблицы внутри пакетного запроса. Результат промежуточной выборки физически записывается во временное хранилище СУБД (например, в tempdb для MS SQL Server), после чего к нему можно обращаться из последующих запросов пакета по имени. Временная таблица живёт в рамках менеджера временных таблиц, пока не будет явно уничтожена оператором УНИЧТОЖИТЬ или пока не завершится выполнение пакета.

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

Когда вложенный запрос — уместный выбор

Вложенный запрос оправдан, когда логика проста и результат нужен ровно один раз. Типичные сценарии: фильтрация по агрегату (ГДЕ Сумма > (ВЫБРАТЬ МАКСИМУМ(...))), получение среза по одному условию, соединение с несложной сгруппированной выборкой. В таких случаях дополнительный запрос пакета и отдельная временная таблица только увеличивают текст без выигрыша.

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

Ограничение: если вложенный запрос дублируется в тексте дважды (например, один и тот же подзапрос в соединении и в условии), СУБД может вычислить его дважды. Это прямой сигнал вынести логику во временную таблицу.

Когда временная таблица выигрывает

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

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

Сравнение вариантов

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

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

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

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

Пакет с индексированной ВТ

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

Влияние на производительность и план запроса

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

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

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

Читаемость, отладка и сопровождение кода

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

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

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

Типичные ошибки при использовании обоих подходов

Самая частая ошибка с временными таблицами — выборка «лишних» данных: во временную таблицу помещают все поля и все строки источника, хотя дальше нужны три поля и десятая часть записей. Это напрасно грузит tempdb и замедляет пакет. Вторая ошибка — забытый УНИЧТОЖИТЬ: таблицы копятся в менеджере, занимают память и могут конфликтовать по именам при повторном выполнении.

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

Типичные ошибки
01
Помещение во временную таблицу всех полей и строк без отбора — лишняя нагрузка на tempdb и медленные соединения.
02
Отсутствие УНИЧТОЖИТЬ — накопление временных таблиц и конфликты имён при повторном выполнении пакета.
03
Дублирование одинакового вложенного запроса в тексте — двойное вычисление одного и того же результата.
04
Индексация временной таблицы по полям, которые не участвуют в соединениях, — затраты без выигрыша.
05
Выбор подхода «на глаз» без замера на реальном объёме данных — неожиданная деградация на рабочей базе.

Как проверить, что выбранный вариант оптимален

Единственный надёжный способ — замер. Воспроизведите оба варианта в консоли запросов на копии рабочей базы или на данных сопоставимого объёма, сравните время выполнения и количество логических чтений, если инструмент это показывает. Для клиент-серверного варианта полезно посмотреть план запроса средствами СУБД или через технологический журнал с событием DBMSSQL/DBPOSTGRS.

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

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

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

Количество строк в итоговом результате
Норма

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

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

Различается — где-то изменилась семантика соединений или отборов

Время выполнения на рабочем объёме данных
Норма

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

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

Сильно колеблется или растёт быстрее объёма данных

План запроса
Норма

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

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

Сканирования, огромные промежуточные наборы, предупреждения оптимизатора

FAQ

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

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

Всегда ли нужно писать УНИЧТОЖИТЬ для временных таблиц?

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

Что выбрать для отчёта на СКД — вложенный запрос или временную таблицу?

В СКД набор данных должен возвращать один результирующий запрос, поэтому временные таблицы в тексте набора данных обычно недоступны напрямую. Используйте вложенные запросы, а сложную подготовку данных выносите в объектную настройку СКД или в предварительное заполнение набора данных-объекта программно.

Ускорит ли индексация временной таблицы любой запрос?

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

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

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

Есть ли ограничение на количество временных таблиц в пакете?

Жёсткого практического лимита для типовых задач нет, но каждая таблица расходует ресурсы tempdb и память. Если пакет разрастается до десятков таблиц, это признак того, что логику стоит декомпозировать на отдельные запросы или пересмотреть алгоритм.

Итог

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

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