Почему не рекомендуется использовать вложенные запросы в 1С и какие альтернативы существования они имеют

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

1. Отсутствие статистики и непредсказуемость плана выполнения

Главная техническая проблема вложенного запроса заключается в том, что оптимизатор планировщика СУБД (будь то MS SQL Server или PostgreSQL) не располагает гистограммой распределения и статистикой по промежуточному результату. Рассмотрим, как это происходит на этапе выполнения:

  1. Оптимизатор формирует план выполнения до того, как вложенный запрос реально отработает и вернет набор записей.
  2. Поскольку объемы и структура будущей выборки неизвестны, СУБД вынуждена «угадывать» количество строк (Cardinality Estimation).
  3. На небольших тестовых базах запрос может работать мгновенно, но при увеличении объемов данных в реальной базе оптимизатор ошибается в оценке выборки. В итоге выбирается неоптимальный алгоритм соединения строк — например, вместо Hash Join или Merge Join выбирается Nested Loops (вложенные петли), что приводит к длительным зависаниям сервера СУБД.

2. Невозможность индексации промежуточных данных

Результат вложенного запроса формируется на лету и сохраняется в оперативной памяти или во временной структуре СУБД без каких-либо индексов. Проанализируем ситуацию, когда выполняется соединение реальной таблицы базы данных с вложенным запросом по определённому полю (например, Номенклатура или Договор):

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

3. Проблемы с оптимизатором Системы компоновки данных (СКД)

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

  1. Невозможность проталкивания условий (Predicate Pushdown): СКД не всегда может «протолкнуть» пользовательские отборы внутрь вложенного запроса. Из-за этого из базы сначала считываются абсолютно все записи, и только потом на весь массив накладывается фильтр.
  2. Искажение логики и пустые наборы: В случаях, когда во вложенном запросе используются объединения (ОБЪЕДИНИТЬ ВСЕ) или соединения с другими временными структурами, динамическая оптимизация СКД может некорректно вырезать «ненужные» поля и привести к получению пустого результата или вызову ошибки выполнения.

4. Риск возникновения коррелированных подзапросов

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

Посмотрим на пример: если во внешнем источнике содержится 50 000 документов, а во вложенном запросе определяется срез последних цен, подзапрос может выполниться 50 000 раз. Алгоритмическая сложность возрастает до O(N²), что приводит к полной остановке работы системы.

5. Сложность поддержки, отладки и чтения кода

С точки зрения чистой разработки и командной работы, вложенные запросы обладают следующими недостатками:

  1. Ненаглядность: Текст с глубокой вложенностью сложно читать и визуально анализировать.
  2. Трудность отладки: Промежуточный результат вложенного запроса нельзя посмотреть «в разрезе». Чтобы найти ошибку, программисту приходится вручную копировать и отлаживать фрагменты кода.
  3. Ограничения Конструктора запросов: Конструктор запросов 1С неудобно работает со сложными вложенными связями, тогда как с пакетами запросов он справляется идеально.
  4. Отсутствие повторного использования: Если выборка из подзапроса требуется в нескольких местах пакета, текст приходится дублировать, из-за чего система выполняет одну и ту же работу неоднократно.

6. Решение проблемы: использование пакетов запросов и временных таблиц

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

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

7. Практический пример рефакторинга кода

Рассмотрев теорию, проанализируем пример нерекомендуемого запроса со соединением с подзапросом и перепишем его на правильную структуру с использованием временных таблиц.

Нерекомендуемый вариант (соединение со вложенным запросом):


ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Продажи.Количество КАК Количество,
    Цены.Цена КАК Цена
ИЗ
    РегистрНакопления.Продажи КАК Продажи
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
            ЦеныНоменклатуры.Цена КАК Цена
        ИЗ
            РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатуры
    ) КАК Цены
    ПО Продажи.Номенклатура = Цены.Номенклатура

Рекомендуемый вариант (пакет запросов с временной таблицей и индексацией):


ВЫБРАТЬ
    ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
    ЦеныНоменклатуры.Цена КАК Цена
ПОМЕСТИТЬ ВТ_Цены
ИЗ
    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатуры

ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Продажи.Количество КАК Количество,
    ВТ_Цены.Цена КАК Цена
ИЗ
    РегистрНакопления.Продажи КАК Продажи
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цены КАК ВТ_Цены
    ПО Продажи.Номенклатура = ВТ_Цены.Номенклатура
;

УНИЧТОЖИТЬ ВТ_Цены

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

← На главную