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

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

Как выполнить пакет запросов в 1С

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

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

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

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

Что такое пакетный запрос и когда он нужен

Пакетным называется запрос, в тексте которого содержится несколько операторов ВЫБРАТЬ, разделённых символом «;». Все они выполняются последовательно в рамках одного вызова. Между собой части пакета связываются временными таблицами: результат одного запроса помещается во временную таблицу и далее используется в следующих запросах пакета.

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

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

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

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

Пример пакета из трёх запросов:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| РеализацияТовары.Ссылка КАК Документ,

| РеализацияТовары.Номенклатура КАК Номенклатура,

| СУММА(РеализацияТовары.Количество) КАК Количество

|ПОМЕСТИТЬ ВТ_Отгрузки

|ИЗ

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

|ГДЕ

| РеализацияТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода

|

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

| РеализацияТовары.Ссылка,

| РеализацияТовары.Номенклатура

|;

|

|ВЫБРАТЬ

| ВТ_Отгрузки.Номенклатура КАК Номенклатура,

| СУММА(ВТ_Отгрузки.Количество) КАК ВсегоОтгружено

|ПОМЕСТИТЬ ВТ_Итоги

|ИЗ

| ВТ_Отгрузки КАК ВТ_Отгрузки

|

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

| ВТ_Отгрузки.Номенклатура

|;

|

|ВЫБРАТЬ

| ВТ_Итоги.Номенклатура КАК Номенклатура,

| ВТ_Итоги.ВсегоОтгружено КАК ВсегоОтгружено,

| Цены.Цена КАК Цена

|ИЗ

| ВТ_Итоги КАК ВТ_Итоги

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены

| ПО ВТ_Итоги.Номенклатура = Цены.Номенклатура";

Все параметры (&НачалоПериода, &КонецПериода, &Период) устанавливаются один раз через УстановитьПараметр() — они видны всем запросам пакета. Это избавляет от необходимости дублировать установку параметров для каждой части.

Как выполнить пакет и получить каждый результат

Для выполнения пакетного запроса используется метод ВыполнитьПакет(). Он возвращает массив, где каждый элемент — результат соответствующего запроса пакета (объект «РезультатЗапроса»). Индексация в массиве идёт с нуля и соответствует порядку запросов в тексте.

МассивРезультатов = Запрос.ВыполнитьПакет();

// МассивРезультатов[0] — результат первого запроса (ПОМЕСТИТЬ ВТ_Отгрузки)

// МассивРезультатов[1] — результат второго запроса (ПОМЕСТИТЬ ВТ_Итоги)

// МассивРезультатов[2] — итоговая выборка

Выборка = МассивРезультатов[2].Выбрать();

Пока Выборка.Следующий() Цикл

// обработка строки

КонецЦикла;

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

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

Способы выполнения пакета

Выполнить()

Возвращает только результат последнего запроса. Подходит, когда промежуточные данные не нужны.

ВыполнитьПакет()

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

ВыполнитьПакетСПромежуточнымиДанными()

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

Работа с менеджером временных таблиц

Временные таблицы привязаны к менеджеру временных таблиц (объект «МенеджерВременныхТаблиц»). Если менеджер не задан явно, платформа создаёт его неявно для каждого запроса, и таблицы живут до конца выполнения пакета. Для большинства задач этого достаточно.

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

МенеджерВТ = Новый МенеджерВременныхТаблиц;

// Первый запрос: загружаем таблицу значений во временную таблицу

ЗапросЗагрузки = Новый Запрос;

ЗапросЗагрузки.МенеджерВременныхТаблиц = МенеджерВТ;

ЗапросЗагрузки.Текст =

"ВЫБРАТЬ

| Таб.Номенклатура КАК Номенклатура,

| Таб.Количество КАК Количество

|ПОМЕСТИТЬ ВТ_Данные

|ИЗ

| &ТаблицаЗначений КАК Таб";

ЗапросЗагрузки.УстановитьПараметр("ТаблицаЗначений", ПодготовленнаяТЗ);

ЗапросЗагрузки.Выполнить();

// Второй запрос: используем ту же временную таблицу

ЗапросОсновной = Новый Запрос;

ЗапросОсновной.МенеджерВременныхТаблиц = МенеджерВТ;

ЗапросОсновной.Текст =

"ВЫБРАТЬ

| ВТ_Данные.Номенклатура КАК Номенклатура,

| Остатки.КоличествоОстаток КАК Остаток

|ИЗ

| ВТ_Данные КАК ВТ_Данные

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки

| ПО ВТ_Данные.Номенклатура = Остатки.Номенклатура";

Результат = ЗапросОсновной.Выполнить();

После завершения работы менеджер временных таблиц рекомендуется закрывать методом Закрыть() — это освобождает ресурсы СУБД, особенно при работе с крупными таблицами в циклах.

Удаление временных таблиц и управление памятью

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

Запрос.Текст =

"УНИЧТОЖИТЬ ВТ_Отгрузки

|;

|

|ВЫБРАТЬ

| ВТ_Итоги.Номенклатура КАК Номенклатура

|ИЗ

| ВТ_Итоги КАК ВТ_Итоги";

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

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

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

Типичные ошибки при работе с пакетами

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

Вторая группа ошибок связана с индексами результатов пакета. Разработчик добавляет в начало пакета ещё один запрос, а код обращения к МассивРезультатов[2] остаётся прежним — и программа молча обрабатывает не ту выборку. Надёжнее вычислять индекс последнего результата как МассивРезультатов.ВГраница() или использовать Выполнить() для итоговой выборки.

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

Типичные ошибки
01
Отсутствие точки с запятой между запросами пакета — весь текст воспринимается как один запрос и падает с синтаксической ошибкой
02
Дублирование имён временных таблиц в одном менеджере — ошибка выполнения на этапе ПОМЕСТИТЬ
03
Обращение к результату пакета по неверному индексу после добавления нового запроса — обработка чужой выборки без явной ошибки
04
Соединение с виртуальной таблицей регистра без отбора по периоду и измерениям — резкое замедление пакета на больших базах
05
Отсутствие Закрыть() у менеджера временных таблиц в длительном цикле — накопление временных данных и рост потребления ресурсов СУБД

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

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

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

Контрольные точки для проверки корректности пакета:

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

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

Количество записей во временной таблице
Норма

Совпадает с ожидаемым числом отобранных строк по условию

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

Ноль строк или в разы больше ожидаемого — ошибка в условии или в соединении (декартово произведение)

Итоговые суммы финальной выборки
Норма

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

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

Суммы завышены кратно числу строк соединяемой таблицы — дублирование при соединении

Время выполнения пакета
Норма

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

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

Растёт от запуска к запуску — вероятно, не очищаются временные таблицы

Когда пакетный запрос не поможет

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

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

Маршрут решения

Как выбрать подход

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

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

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

Частые вопросы о пакетных запросах в 1С

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

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

Почему ВыполнитьПакет() возвращает пустые результаты для запросов с ПОМЕСТИТЬ?

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

Как узнать, сколько записей попало во временную таблицу?

Проще всего — через результат соответствующего элемента массива ВыполнитьПакет(): у результата запроса с ПОМЕСТИТЬ можно вызвать Выбрать() и получить единственную строку с количеством записей в некоторых версиях платформы. Универсальный способ — добавить в пакет отдельный запрос ВЫБРАТЬ КОЛИЧЕСТВО(*) из временной таблицы.

Что быстрее: пакетный запрос или несколько отдельных запросов?

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

Нужно ли удалять временные таблицы, если пакет выполняется один раз?

Для однократного выполнения с неявным менеджером временных таблиц явное удаление не обязательно — таблицы уничтожатся автоматически. УНИЧТОЖИТЬ имеет смысл внутри длинного пакета, когда крупная таблица больше не нужна, и в циклах с общим менеджером, где временные данные накапливаются.

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

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