Выполнение пакета запросов в 1С 8.3 с примерами
Содержание
Пакет запросов — это несколько запросов в одном тексте, разделённых точкой с запятой. Выполняются они за одно обращение к базе, а промежуточные результаты передаются между запросами через временные таблицы. В коде пакет запускают методом Выполнить() — он вернёт результат последнего запроса пакета, или ВыполнитьПакет() — он вернёт массив результатов по каждому запросу.
Пакетный запрос нужен там, где одним ВЫБРАТЬ не обойтись: сложный отчёт в несколько шагов, повторяющийся подзапрос, который дешевле посчитать один раз, или соединение больших наборов, которое тормозит без промежуточного индекса. Разбиваешь задачу на этапы, складываешь результат каждого в отдельную временную таблицу и работаешь с ней дальше как с обычной таблицей базы.
Ниже — синтаксис пакета, работа с временными таблицами и менеджером временных таблиц, приёмы ускорения и ошибки, на которых чаще всего спотыкаются.
Из чего состоит пакет и когда он выгоден
Каждый запрос в пакете — самостоятельный ВЫБРАТЬ. Разделитель между ними — ;. Запросы выполняются строго по порядку сверху вниз, поэтому таблицу, созданную во втором запросе, нельзя использовать в первом.
Пакет оправдан в нескольких случаях:
- 📊 Многошаговый отчёт. Сначала собираем продажи, потом остатки, потом соединяем — каждый шаг в своей временной таблице.
- 🔁 Повторяющийся подзапрос. Если один и тот же набор данных нужен в нескольких соединениях, считаем его один раз и кладём во временную таблицу.
- ⚡ Соединение больших наборов. Виртуальные таблицы остатков и оборотов тяжело соединять напрямую — через временную таблицу с индексом получается быстрее.
- 🧮 Схема компоновки данных. В СКД пакет с временными таблицами — стандартный приём для набора данных «Запрос».
Если же задача решается одним запросом без повторов и тяжёлых соединений, пакет не нужен — он только усложнит текст.
Как написать и выполнить пакет запросов
Соберём продажи за период в временную таблицу, а вторым запросом сгруппируем их по номенклатуре.
ВЫБРАТЬ
РеализацияТовары.Номенклатура КАК Номенклатура,
РеализацияТовары.Количество КАК Количество,
РеализацияТовары.Сумма КАК Сумма
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ГДЕ
РеализацияТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
ВТ_Продажи.Номенклатура КАК Номенклатура,
СУММА(ВТ_Продажи.Количество) КАК Количество,
СУММА(ВТ_Продажи.Сумма) КАК Сумма
ИЗ
ВТ_Продажи КАК ВТ_Продажи
СГРУППИРОВАТЬ ПО
ВТ_Продажи.Номенклатура
УПОРЯДОЧИТЬ ПО
Сумма УБЫВ
Ключевое слово ПОМЕСТИТЬ ВТ_Продажи после списка полей отправляет результат не в выборку, а во временную таблицу с таким именем. Второй запрос обращается к ней уже как к источнику в ИЗ.
В коде пакет выполняют так:
Запрос = Новый Запрос;
Запрос.Текст = ТекстПакета;
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
// результат последнего запроса пакета
Результат = Запрос.Выполнить();
// либо массив результатов — по одному на каждый запрос
МассивРезультатов = Запрос.ВыполнитьПакет();
ИтоговыйРезультат = МассивРезультатов[1];
Разница между методами простая:
| Метод | Что возвращает |
|---|---|
Выполнить() | Результат последнего запроса пакета |
ВыполнитьПакет() | Массив из РезультатЗапроса по каждому запросу (для запроса с ПОМЕСТИТЬ элемент пустой) |
ВыполнитьПакетСИнформациейОВременныхТаблицах() | То же, но для запросов, создающих временную таблицу, возвращает сведения о её колонках — этим пользуются консоли запросов |
Нумерация в массиве ВыполнитьПакет() идёт с нуля и совпадает с порядком запросов в тексте, включая те, что только создают временные таблицы. В примере выше результат группировки — элемент [1].
Временные таблицы — ядро пакетного запроса
Временная таблица существует в рамках одного выполнения запроса и видна всем последующим запросам пакета. Управляют ею тремя конструкциями:
ПОМЕСТИТЬ ИмяТаблицы— создаёт таблицу из результата запроса.ИНДЕКСИРОВАТЬ ПО Поле— строит индекс по указанным полям (ставится в конце запроса, который создаёт таблицу).УНИЧТОЖИТЬ ИмяТаблицы— удаляет таблицу досрочно, отдельным запросом в пакете.
Имена временных таблиц принято начинать с префикса ВТ_ — это не требование платформы, а общая договорённость, которая делает текст читаемым.
Соединение двух временных таблиц
Главная сила пакета раскрывается, когда соединяешь несколько промежуточных наборов. Классический пример — сопоставить продажи и остатки по номенклатуре. Каждый набор собираем отдельным запросом, индексируем по общему полю и соединяем третьим:
// 1. Продажи за период
ВЫБРАТЬ
Продажи.Номенклатура КАК Номенклатура,
СУММА(Продажи.Количество) КАК Продано
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Продажи.Номенклатура
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
// 2. Остатки на дату
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.КоличествоОстаток КАК Остаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&КонецПериода, ) КАК Остатки
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
// 3. Соединяем по индексированному полю
ВЫБРАТЬ
ЕСТЬNULL(ВТ_Продажи.Номенклатура, ВТ_Остатки.Номенклатура) КАК Номенклатура,
ЕСТЬNULL(ВТ_Продажи.Продано, 0) КАК Продано,
ЕСТЬNULL(ВТ_Остатки.Остаток, 0) КАК Остаток
ИЗ
ВТ_Продажи КАК ВТ_Продажи
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки
ПО ВТ_Продажи.Номенклатура = ВТ_Остатки.Номенклатура
Виртуальную таблицу остатков мы посчитали один раз и положили в ВТ_Остатки, обе таблицы проиндексировали по Номенклатура, а ПОЛНОЕ СОЕДИНЕНИЕ с ЕСТЬNULL не теряет позиции, которые есть только в продажах или только в остатках. Напрямую, без временных таблиц, такое соединение виртуальных таблиц работало бы заметно медленнее.
Менеджер временных таблиц
По умолчанию временные таблицы живут только до конца Выполнить(). Если нужно создать таблицу в одном запросе, а обратиться к ней из другого (например, разбить логику на несколько отдельных пакетов или передать таблицу в другой метод), подключают менеджер временных таблиц:
МВТ = Новый МенеджерВременныхТаблиц;
Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МВТ;
Запрос1.Текст = "ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ_Продажи ИЗ ...";
Запрос1.Выполнить();
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МВТ;
Запрос2.Текст = "ВЫБРАТЬ ... ИЗ ВТ_Продажи КАК ВТ_Продажи ...";
Результат = Запрос2.Выполнить();
МВТ.Закрыть();
Оба запроса видят таблицу ВТ_Продажи, потому что ссылаются на один и тот же менеджер. После работы менеджер нужно закрывать методом Закрыть() — иначе временные таблицы останутся висеть в памяти. Именно через менеджер временных таблиц СКД передаёт промежуточные наборы между запросами схемы.
Как ускорить пакетный запрос
Пакет с временными таблицами сам по себе не быстрее — выигрыш даёт грамотная организация этапов. Основные приёмы:
Чуть подробнее о главном. Индекс по полю соединения — это то, ради чего временные таблицы часто и заводят. Соединение двух неиндексированных наборов платформа выполняет перебором, и на больших данных это и есть источник тормозов. Поставьте ИНДЕКСИРОВАТЬ ПО тем полям, по которым идёт СОЕДИНЕНИЕ в следующем запросе.
Ранний отбор важнее индексов. Чем меньше строк попало во временную таблицу, тем быстрее всё дальнейшее. Переносите условия ГДЕ в самый первый запрос, не тащите лишнее «на потом».
Материализация виртуальных таблиц. Обращение к Остатки, Обороты, ОстаткиИОбороты — дорогая операция. Если результат нужен в нескольких соединениях, посчитайте его один раз, положите во временную таблицу и дальше соединяйте уже с ней.
Пакет или обычный запрос: что выбрать
Пакет не заменяет одиночный запрос, у каждого своя область.
| Ситуация | Что использовать |
|---|---|
| Простая выборка из одной-двух таблиц | Обычный запрос |
| Один подзапрос используется несколько раз | Пакет с временной таблицей |
| Нужно соединить большие наборы с индексом | Пакет |
| Виртуальная таблица считается один раз | Обычный запрос |
| Многошаговый отчёт, набор данных СКД | Пакет |
Правило простое: пока логика укладывается в один читаемый ВЫБРАТЬ без повторов — оставайтесь на одиночном запросе. Как только появляется повторяющийся подзапрос или тяжёлое соединение — выносите этап во временную таблицу.
Динамическая сборка пакета и передача данных извне
Текст пакета можно собирать в коде — например, добавлять запросы в зависимости от настроек отчёта или выбранных пользователем разрезов. Здесь действует главное правило безопасности запросов 1С: значения передавайте только через параметры (&Параметр и УстановитьПараметр), а не склеивайте в текст. Конкатенация значений в текст запроса — источник ошибок с типами и датами.
Сам текст при динамической сборке наращивают построчно, добавляя разделитель между запросами. Например, дополнительный этап подключают по условию:
ТекстПакета = ПервыйЗапрос;
Если НужнаДетализация Тогда
ТекстПакета = ТекстПакета + "
|;
|" + ЗапросДетализации;
КонецЕсли;
Разделитель ; ставится между запросами, а не после последнего. Вертикальная черта | в начале строк — обязательное обозначение переноса внутри строкового литерала 1С. Значения отбора при этом по-прежнему идут параметрами, в текст попадает только структура запроса.
Передать во временную таблицу данные из кода тоже можно. Готовую таблицу значений кладут в параметр и используют как источник:
ВЫБРАТЬ
ВходящаяТаблица.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Отбор
ИЗ
&ВходящаяТаблица КАК ВходящаяТаблица
ИНДЕКСИРОВАТЬ ПО
Номенклатура
Запрос.УстановитьПараметр("ВходящаяТаблица", ТаблицаНоменклатуры);
У таблицы значений, которую передаёте параметром, колонки должны быть типизированы — иначе платформа не сможет построить временную таблицу. Задавайте типы колонок при создании таблицы значений.
Этот приём удобен, когда отбор формируется в форме или приходит из внешней системы: получили список, положили в таблицу значений, передали в запрос и дальше соединяете с ней обычными средствами языка запросов.
Как найти узкое место: отладка и замер
Если пакет работает медленно, сначала нужно понять, какой именно запрос тормозит.
- Консоль запросов. Внешняя обработка «Консоль запросов» выполняет пакет и показывает результат каждого запроса отдельно, с временем выполнения. Удобно обкатывать текст до переноса в код.
- Замер производительности. В конфигураторе:
Отладка → Замер производительности. Запускаете сценарий, останавливаете замер и смотрите, сколько времени заняла строка сВыполнить(). - Технологический журнал. Для клиент-серверной базы в журнале видны события выполнения запросов на стороне сервера и СУБД.
- План запроса в СУБД. Если база на MS SQL Server или PostgreSQL, тяжёлый запрос анализируют по плану на стороне сервера — видно, где идёт перебор вместо поиска по индексу.
Отлаживая пакет через ВыполнитьПакет(), можно временно вывести количество строк каждого промежуточного результата — так сразу видно, на каком этапе данных стало неожиданно много.
Частые ошибки при работе с пакетами
- ❌ Обращение к временной таблице до её создания. Запросы выполняются по порядку; таблица из третьего запроса не видна во втором. Проверьте последовательность.
- ❌
Выполнить()вместоВыполнитьПакет(). Если нужны результаты нескольких запросов, а вызванВыполнить(), вернётся только последний — остальные «потеряются». - ❌ Соединение без индекса. Самая частая причина тормозов: временные таблицы соединяются по неиндексированному полю. Добавьте
ИНДЕКСИРОВАТЬ ПО. - ❌ Незакрытый менеджер временных таблиц. При работе через общий
МенеджерВременныхТаблицтаблицы не исчезают сами — не забывайтеЗакрыть(). - ❌ Лишние колонки во временной таблице. Каждое ненужное поле — это память и лишние данные при соединении. Складывайте только то, что действительно используется дальше.
Чем `ВыполнитьПакет()` отличается от `Выполнить()`?
Выполнить() возвращает результат последнего запроса пакета, ВыполнитьПакет() — массив результатов по каждому запросу. Если из пакета нужны данные нескольких запросов, используйте ВыполнитьПакет().Нужен ли менеджер временных таблиц, если пакет один?
Выполнить(), а используют в другом.Можно ли поместить во временную таблицу таблицу значений из кода?
ИЗ как &Параметр КАК ... с конструкцией ПОМЕСТИТЬ. Колонки таблицы значений должны быть типизированы.