Выполнение пакета запросов в 1С 8.3 с примерами

· 9 мин

Содержание

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

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

Ниже — синтаксис пакета, работа с временными таблицами и менеджером временных таблиц, приёмы ускорения и ошибки, на которых чаще всего спотыкаются.

Из чего состоит пакет и когда он выгоден

Каждый запрос в пакете — самостоятельный ВЫБРАТЬ. Разделитель между ними — ;. Запросы выполняются строго по порядку сверху вниз, поэтому таблицу, созданную во втором запросе, нельзя использовать в первом.

Пакет оправдан в нескольких случаях:

  • 📊 Многошаговый отчёт. Сначала собираем продажи, потом остатки, потом соединяем — каждый шаг в своей временной таблице.
  • 🔁 Повторяющийся подзапрос. Если один и тот же набор данных нужен в нескольких соединениях, считаем его один раз и кладём во временную таблицу.
  • ⚡ Соединение больших наборов. Виртуальные таблицы остатков и оборотов тяжело соединять напрямую — через временную таблицу с индексом получается быстрее.
  • 🧮 Схема компоновки данных. В СКД пакет с временными таблицами — стандартный приём для набора данных «Запрос».

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

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

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

ВЫБРАТЬ
	РеализацияТовары.Номенклатура КАК Номенклатура,
	РеализацияТовары.Количество КАК Количество,
	РеализацияТовары.Сумма КАК Сумма
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
	Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ГДЕ
	РеализацияТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода

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

ВЫБРАТЬ
	ВТ_Продажи.Номенклатура КАК Номенклатура,
	СУММА(ВТ_Продажи.Количество) КАК Количество,
	СУММА(ВТ_Продажи.Сумма) КАК Сумма
ИЗ
	ВТ_Продажи КАК ВТ_Продажи
СГРУППИРОВАТЬ ПО
	ВТ_Продажи.Номенклатура
УПОРЯДОЧИТЬ ПО
	Сумма УБЫВ

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

В коде пакет выполняют так:

Запрос = Новый Запрос;
Запрос.Текст = ТекстПакета;
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода",  КонецПериода);

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

// либо массив результатов — по одному на каждый запрос
МассивРезультатов = Запрос.ВыполнитьПакет();
ИтоговыйРезультат = МассивРезультатов[1];

Разница между методами простая:

МетодЧто возвращает
Выполнить()Результат последнего запроса пакета
ВыполнитьПакет()Массив из РезультатЗапроса по каждому запросу (для запроса с ПОМЕСТИТЬ элемент пустой)
ВыполнитьПакетСИнформациейОВременныхТаблицах()То же, но для запросов, создающих временную таблицу, возвращает сведения о её колонках — этим пользуются консоли запросов

Нумерация в массиве ВыполнитьПакет() идёт с нуля и совпадает с порядком запросов в тексте, включая те, что только создают временные таблицы. В примере выше результат группировки — элемент [1].

Временные таблицы — ядро пакетного запроса

Временная таблица существует в рамках одного выполнения запроса и видна всем последующим запросам пакета. Управляют ею тремя конструкциями:

  • ПОМЕСТИТЬ ИмяТаблицы — создаёт таблицу из результата запроса.
  • ИНДЕКСИРОВАТЬ ПО Поле — строит индекс по указанным полям (ставится в конце запроса, который создаёт таблицу).
  • УНИЧТОЖИТЬ ИмяТаблицы — удаляет таблицу досрочно, отдельным запросом в пакете.

Имена временных таблиц принято начинать с префикса ВТ_ — это не требование платформы, а общая договорённость, которая делает текст читаемым.

Соединение двух временных таблиц

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

// 1. Продажи за период
ВЫБРАТЬ
	Продажи.Номенклатура КАК Номенклатура,
	СУММА(Продажи.Количество) КАК Продано
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
	РегистрНакопления.Продажи КАК Продажи
ГДЕ
	Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
	Продажи.Номенклатура
ИНДЕКСИРОВАТЬ ПО
	Номенклатура
;

// 2. Остатки на дату
ВЫБРАТЬ
	Остатки.Номенклатура КАК Номенклатура,
	Остатки.КоличествоОстаток КАК Остаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
	РегистрНакопления.ТоварыНаСкладах.Остатки(&КонецПериода, ) КАК Остатки
ИНДЕКСИРОВАТЬ ПО
	Номенклатура
;

// 3. Соединяем по индексированному полю
ВЫБРАТЬ
	ЕСТЬNULL(ВТ_Продажи.Номенклатура, ВТ_Остатки.Номенклатура) КАК Номенклатура,
	ЕСТЬNULL(ВТ_Продажи.Продано, 0) КАК Продано,
	ЕСТЬNULL(ВТ_Остатки.Остаток, 0) КАК Остаток
ИЗ
	ВТ_Продажи КАК ВТ_Продажи
	ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки
	ПО ВТ_Продажи.Номенклатура = ВТ_Остатки.Номенклатура

Виртуальную таблицу остатков мы посчитали один раз и положили в ВТ_Остатки, обе таблицы проиндексировали по Номенклатура, а ПОЛНОЕ СОЕДИНЕНИЕ с ЕСТЬNULL не теряет позиции, которые есть только в продажах или только в остатках. Напрямую, без временных таблиц, такое соединение виртуальных таблиц работало бы заметно медленнее.

Менеджер временных таблиц

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

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

Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МВТ;
Запрос1.Текст = "ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ_Продажи ИЗ ...";
Запрос1.Выполнить();

Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МВТ;
Запрос2.Текст = "ВЫБРАТЬ ... ИЗ ВТ_Продажи КАК ВТ_Продажи ...";
Результат = Запрос2.Выполнить();

МВТ.Закрыть();

Оба запроса видят таблицу ВТ_Продажи, потому что ссылаются на один и тот же менеджер. После работы менеджер нужно закрывать методом Закрыть() — иначе временные таблицы останутся висеть в памяти. Именно через менеджер временных таблиц СКД передаёт промежуточные наборы между запросами схемы.

Как ускорить пакетный запрос

Пакет с временными таблицами сам по себе не быстрее — выигрыш даёт грамотная организация этапов. Основные приёмы:

Оптимизация пакетного запроса
Выполнено: 0 / 7

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

Ранний отбор важнее индексов. Чем меньше строк попало во временную таблицу, тем быстрее всё дальнейшее. Переносите условия ГДЕ в самый первый запрос, не тащите лишнее «на потом».

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

Пакет или обычный запрос: что выбрать

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

СитуацияЧто использовать
Простая выборка из одной-двух таблицОбычный запрос
Один подзапрос используется несколько разПакет с временной таблицей
Нужно соединить большие наборы с индексомПакет
Виртуальная таблица считается один разОбычный запрос
Многошаговый отчёт, набор данных СКДПакет

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

Динамическая сборка пакета и передача данных извне

Текст пакета можно собирать в коде — например, добавлять запросы в зависимости от настроек отчёта или выбранных пользователем разрезов. Здесь действует главное правило безопасности запросов 1С: значения передавайте только через параметры (&Параметр и УстановитьПараметр), а не склеивайте в текст. Конкатенация значений в текст запроса — источник ошибок с типами и датами.

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

ТекстПакета = ПервыйЗапрос;
Если НужнаДетализация Тогда
	ТекстПакета = ТекстПакета + "
	|;
	|" + ЗапросДетализации;
КонецЕсли;

Разделитель ; ставится между запросами, а не после последнего. Вертикальная черта | в начале строк — обязательное обозначение переноса внутри строкового литерала 1С. Значения отбора при этом по-прежнему идут параметрами, в текст попадает только структура запроса.

Передать во временную таблицу данные из кода тоже можно. Готовую таблицу значений кладут в параметр и используют как источник:

ВЫБРАТЬ
	ВходящаяТаблица.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Отбор
ИЗ
	&ВходящаяТаблица КАК ВходящаяТаблица

ИНДЕКСИРОВАТЬ ПО
	Номенклатура
Запрос.УстановитьПараметр("ВходящаяТаблица", ТаблицаНоменклатуры);

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

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

Как найти узкое место: отладка и замер

Если пакет работает медленно, сначала нужно понять, какой именно запрос тормозит.

  1. Консоль запросов. Внешняя обработка «Консоль запросов» выполняет пакет и показывает результат каждого запроса отдельно, с временем выполнения. Удобно обкатывать текст до переноса в код.
  2. Замер производительности. В конфигураторе: Отладка → Замер производительности. Запускаете сценарий, останавливаете замер и смотрите, сколько времени заняла строка с Выполнить().
  3. Технологический журнал. Для клиент-серверной базы в журнале видны события выполнения запросов на стороне сервера и СУБД.
  4. План запроса в СУБД. Если база на MS SQL Server или PostgreSQL, тяжёлый запрос анализируют по плану на стороне сервера — видно, где идёт перебор вместо поиска по индексу.

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

Частые ошибки при работе с пакетами

  • ❌ Обращение к временной таблице до её создания. Запросы выполняются по порядку; таблица из третьего запроса не видна во втором. Проверьте последовательность.
  • ❌ Выполнить() вместо ВыполнитьПакет(). Если нужны результаты нескольких запросов, а вызван Выполнить(), вернётся только последний — остальные «потеряются».
  • ❌ Соединение без индекса. Самая частая причина тормозов: временные таблицы соединяются по неиндексированному полю. Добавьте ИНДЕКСИРОВАТЬ ПО.
  • ❌ Незакрытый менеджер временных таблиц. При работе через общий МенеджерВременныхТаблиц таблицы не исчезают сами — не забывайте Закрыть().
  • ❌ Лишние колонки во временной таблице. Каждое ненужное поле — это память и лишние данные при соединении. Складывайте только то, что действительно используется дальше.
Чем `ВыполнитьПакет()` отличается от `Выполнить()`?
Выполнить() возвращает результат последнего запроса пакета, ВыполнитьПакет() — массив результатов по каждому запросу. Если из пакета нужны данные нескольких запросов, используйте ВыполнитьПакет().
Нужен ли менеджер временных таблиц, если пакет один?
Нет. Внутри одного пакета временные таблицы и так видны всем последующим запросам. Менеджер нужен, когда таблицу создают в одном Выполнить(), а используют в другом.
Можно ли поместить во временную таблицу таблицу значений из кода?
Да. Передайте её параметром и укажите в ИЗ как &Параметр КАК ... с конструкцией ПОМЕСТИТЬ. Колонки таблицы значений должны быть типизированы.
Сколько запросов можно включить в один пакет?
Жёсткого ограничения нет, но здравый предел — несколько этапов. Если запросов становится слишком много, логику стоит разбить на отдельные вызовы с менеджером временных таблиц.
Почему пакет с временными таблицами быстрее вложенных подзапросов?
Потому что промежуточный результат считается один раз и его можно проиндексировать. Вложенный подзапрос в соединении платформа может пересчитывать многократно и без индекса.