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

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

Материал будет полезен как начинающим программистам , так и опытным специалистам, которые хотят углубить знания в области производительности. Все примеры тестировались на платформе 1С:Предприятие 8.3.20+, но большинство принципов актуальны и для более ранних версий.

1. Что такое пакетные запросы и зачем они нужны

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

  • 🔹 Сокращает сетевой трафик между клиентом и сервером, так как данные передаются одним блоком.
  • 🔹 Уменьшает нагрузку на СУБД, поскольку оптимизатор запросов может построить единый план выполнения.
  • 🔹 Гарантирует атомарность операций: либо все команды в пакете выполняются успешно, либо ни одна.
  • 🔹 Упрощает код, сокращая количество повторяющихся конструкций (например, объединение таблиц или фильтры).

Типичные сценарии использования пакетных запросов:

  • 📊 Формирование сложных отчётов с несколькими источниками данных.
  • 🔄 Миграция данных между базами или версиями конфигурации.
  • 🔧 Массовое обновление справочников (например, изменение цен номенклатуры).
  • 🤖 Интеграция с внешними системами (обмен через JSON, XML, REST API).

Пример простого пакетного запроса, который получает данные о продажах и остатках товаров:

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

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

"ВЫБРАТЬ

| Продажи.Номенклатура КАК Номенклатура,

| СУММА(Продажи.Количество) КАК Продано,

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

|ИЗ

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

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

| ПО Продажи.Номенклатура = Остатки.Номенклатура

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

| Продажи.Номенклатура,

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

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

⚠️ Внимание: В пакетных запросах нельзя использовать конструкции, которые требуют интерактивного взаимодействия с пользователем (например, ВЫБРАТЬ РАЗРЕШЕННЫЕ или ПОМЕТИТЬ). Также ограничено использование временных таблиц — их нужно создавать заранее.

2. Синтаксис пакетных запросов: от базы к продвинутым техникам

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

  • 📌 Разделитель команд: в пакетном запросе отдельные SQL-подобные команды разделяются точкой с запятой (;). При этом последняя команда может заканчиваться без неё.
  • 📌 Параметры: их можно передавать как для всего пакета, так и для отдельных команд внутри него.
  • 📌 Временные таблицы: если они используются в нескольких командах пакета, их нужно объявить в начале с помощью ВРЕМЕННЫЕ ТАБЛИЦЫ.

Пример пакетного запроса с временными таблицами и параметрами:

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

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

"ВРЕМЕННЫЕ ТАБЛИЦЫ

| ВТ_Продажи,

| ВТ_Остатки

|

|////////////////////////////////////////////////

|// Заполняем временную таблицу продаж

|////////////////////////////////////////////////

|ВЫБРАТЬ

| Продажи.Номенклатура КАК Номенклатура,

| СУММА(Продажи.Количество) КАК Количество

|ПОМЕСТИТЬ ВТ_Продажи

|ИЗ

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

|ГДЕ

| Продажи.Документ.Дата МЕЖДУ &НачалоПериода И &КонецПериода

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

| Продажи.Номенклатура

|

|////////////////////////////////////////////////

|// Заполняем временную таблицу остатков

|////////////////////////////////////////////////

|ВЫБРАТЬ

| Остатки.Номенклатура КАК Номенклатура,

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

|ПОМЕСТИТЬ ВТ_Остатки

|ИЗ

| РегистрНакопления.ОстаткиТоваров.Остатки(&КонецПериода,) КАК Остатки

|

|////////////////////////////////////////////////

|// Основной запрос с объединением данных

|////////////////////////////////////////////////

|ВЫБРАТЬ

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

| ВТ_Продажи.Количество КАК Продано,

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

|ИЗ

| ВТ_Продажи КАК ВТ_Продажи

| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Остатки КАК ВТ_Остатки

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

Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата()));

Запрос.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата()));

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

Обратите внимание на использование комментариев (//) для структурирования кода — это упрощает поддержку и отладку сложных пакетов.

📊 Как часто вы используете пакетные запросы в 1С?
Постоянно, в большинстве задач
Иногда, для сложных отчётов
Редеко, только при необходимости
Никогда не использовал

3. Оптимизация пакетных запросов: 7 практических советов

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

  1. Используйте временные таблицы для промежуточных данных. Это снижает нагрузку на основные таблицы базы данных и ускоряет повторные обращения к одним и тем же данным.
  2. Ограничивайте объём данных на ранних этапах. Например, если вам нужны продажи только за последний месяц, добавьте фильтр по дате в самом первом запросе пакета.
  3. Избегайте ВЫБРАТЬ *. Всегда перечисляйте только необходимые поля — это уменьшает объём передаваемых данных.
  4. Разбивайте большие пакеты на логические блоки. Например, если пакет состоит из 20 команд, разделите его на 2-3 меньших пакета с промежуточными ЗафиксироватьТранзакцию().
  5. Используйте индексы. Убедитесь, что поля, по которым идут соединения или фильтрация, проиндексированы в конфигурации.
  6. Тестируйте производительность. В 1С:Предприятие 8.3.20+ есть встроенный профайлер запросов (Запрос.ПрофайлерВключен = Истина).
  7. Избегайте вложенных пакетных запросов. Они усложняют отладку и могут приводить к неожиданным блокировкам.

Пример оптимизированного пакетного запроса для анализа продаж по регионам:

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

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

"ВРЕМЕННЫЕ ТАБЛИЦЫ ВТ_ПродажиПоРегионам

// Шаг 1: Получаем только необходимые поля с фильтром по дате

ВЫБРАТЬ

Регион.Наименование КАК Регион,

Продажи.Номенклатура КАК Номенклатура,

СУММА(Продажи.Сумма) КАК СуммаПродаж

ПОМЕСТИТЬ ВТ_ПродажиПоРегионам

ИЗ

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

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Клиенты

ПО Продажи.Контрагент = Клиенты.Ссылка

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Регионы КАК Регион

ПО Клиенты.Регион = Регион.Ссылка

ГДЕ

Продажи.Дата МЕЖДУ &НачалоПериода И &КонецПериода

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

Регион.Наименование,

Продажи.Номенклатура

// Шаг 2: Агрегируем данные по регионам

ВЫБРАТЬ

ВТ_ПродажиПоРегионам.Регион КАК Регион,

СУММА(ВТ_ПродажиПоРегионам.СуммаПродаж) КАК ИтогоПродаж,

КОЛИЧЕСТВО(RAZN(ВТ_ПродажиПоРегионам.Номенклатура)) КАК КоличествоНоменклатуры

ИЗ

ВТ_ПродажиПоРегионам КАК ВТ_ПродажиПоРегионам

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

ВТ_ПродажиПоРегионам.Регион

УПОРЯДОЧИТЬ ПО

ИтогоПродаж УБЫВ";

⚠️ Внимание: При работе с большими объёмами данных (более 100 000 строк) пакетные запросы могут блокировать таблицы базы данных на время выполнения. В таких случаях рассмотрите возможность использования фоновых заданий или разбивки пакета на части с задержками между ними.

Определить минимальный набор полей для выборки|

Добавить фильтры на ранних этапах обработки|

Использовать временные таблицы для промежуточных результатов|

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

Разбить большой пакет на логические блоки (при необходимости)|

Включить профайлер для анализа производительности-->

4. Типичные ошибки и как их избежать

Даже опытные разработчики сталкиваются с ошибками при работе с пакетными запросами. Рассмотрим самые распространённые проблемы и способы их решения:

Ошибка Причина Решение
Ошибка при выполнении пакетного запроса: Недопустимый идентификатор Использование несуществующей временной таблицы или опечатка в её имени. Проверьте объявление временных таблиц в начале пакета и их правильное использование в командах.
Транзакция прервана из-за ошибки блокировки Длительное выполнение пакета блокирует таблицы, что приводит к тайм-ауту. Разбейте пакет на меньшие части или используйте Попытка...Исключение для повторных попыток.
Недостаточно памяти для выполнения запроса Слишком большой объём данных обрабатывается в одной транзакции. Ограничьте период выборки или используйте постраничную обработку (ПЕРВЫЕ N).
Неверный тип параметра Переданный параметр не соответствует ожидаемому типу (например, строка вместо даты). Явно преобразуйте параметры к нужному типу перед передачей в запрос.

Одна из самых коварных ошибок — неявное преобразование типов. Например, если в запросе вы сравниваете поле типа Дата со строковым параметром, попытается автоматически преобразовать строку в дату, что может привести к неожиданным результатам или ошибкам. Всегда следите за соответствием типов!

Пример обработки ошибок в пакетном запросе:

Попытка

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

Запрос.Текст = "ВЫБРАТЬ ... "; // Ваш пакетный запрос

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

// Обработка результата

Если НЕ Результат.Пустой() Тогда

// ...

КонецЕсли;

Исключение

Сообщить("Ошибка при выполнении пакетного запроса: " + ОписаниеОшибки());

// Логирование ошибки или повторная попытка

ЗаписатьЖурналРегистрации(НСтр("ru = 'Ошибка пакетного запроса'"),

УровеньЖурналаРегистрации.Ошибка,

,

,

ОписаниеОшибки());

Возврат Ложь;

КонецПопытки;

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

Если пакетный запрос выполняется слишком долго (более 5-10 минут), скорее всего, проблема в одном из следующих факторов:

1. Отсутствие индексов на полях, используемых в условиях ГДЕ или СОЕДИНЕНИЕ.

2. Слишком широкие выборки — например, запрос возвращает миллионы строк, которые потом агрегируются.

3. Блокировки таблиц другими сеансами (проверьте в Журнале регистрации или через Администрирование → Активные пользователи).

4. Неэффективный план выполнения — используйте профайлер запросов, чтобы найти узкие места.

Решения:

- Разбейте запрос на части и выполняйте их последовательно с фиксацией транзакций.

- Добавьте фильтры, чтобы сократить объём обрабатываемых данных.

- Проверьте наличие индексов в конфигураторе (Все функции → Индексы таблиц базы данных).

- Для критичных задач рассмотрите возможность переноса логики на сторону СУБД (хранимые процедуры).

5. Пакетные запросы vs. обычные запросы: когда что использовать

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

Критерий Обычный запрос (Выполнить()) Пакетный запрос (ВыполнитьПакет())
Производительность Ниже (много сетевых обращений) Выше (данные передаются пакетом)
Сложность кода Проще для небольших задач Сложнее (нужно следить за транзакциями и временными таблицами)
Транзакционность Каждый запрос — отдельная транзакция (если не обернуть вручную) Весь пакет выполняется в одной транзакции
Гибкость Легко модифицировать отдельные запросы Изменение одной команды может потребовать переработки всего пакета
Подходит для Простые выборки, небольшие отчёты Сложные отчёты, массовая обработка данных, интеграции

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

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

Пример, когда обычный запрос предпочтительнее:

// Простой запрос для получения списка номенклатуры

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

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

"ВЫБРАТЬ

| Номенклатура.Ссылка КАК Ссылка,

| Номенклатура.Наименование КАК Наименование

|ИЗ

| Справочник.Номенклатура КАК Номенклатура

|ГДЕ

| Номенклатура.ПометкаУдаления = ЛОЖЬ

|УПОРЯДОЧИТЬ ПО

| Наименование";

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

💡

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

6. Продвинутые техники: динамические пакеты и интеграция с внешними системами

Для сложных задач стандартных пакетных запросов может быть недостаточно. Рассмотрим две продвинутые техники:

6.1. Динамическое формирование пакетных запросов

Иногда текст запроса нужно формировать программно — например, когда количество таблиц или условий зависит от пользовательских настроек. Для этого можно использовать СтрокаЗапроса и динамическое добавление условий:

ТекстЗапроса = Новый СтроковыйПостроитель;

ТекстЗапроса.Добавить("ВЫБРАТЬ");

ТекстЗапроса.Добавить(" Номенклатура.Наименование КАК Наименование,");

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

Если ФлагСтоимость Тогда

ТекстЗапроса.Добавить(" Номенклатура.Стоимость КАК Стоимость,");

КонецЕсли;

Если ФлагОстатки Тогда

ТекстЗапроса.Добавить(" Остатки.КоличествоОстаток КАК Остаток,");

КонецЕсли;

// Удаляем последнюю запятую

ТекстЗапроса.УдалитьСимволыСКонца(1);

ТекстЗапроса.Добавить("ИЗ");

ТекстЗапроса.Добавить(" Справочник.Номенклатура КАК Номенклатура");

// Динамически добавляем соединения

Если ФлагОстатки Тогда

ТекстЗапроса.Добавить(" ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки КАК Остатки");

ТекстЗапроса.Добавить(" ПО Номенклатура.Ссылка = Остатки.Номенклатура");

КонецЕсли;

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

Запрос.Текст = ТекстЗапроса.ПолучитьСтроку();

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

6.2. Использование пакетных запросов для обмена данными

При интеграции с внешними системами (например, через REST API или JSON) пакетные запросы помогают подготовить данные для экспорта или обработать полученную информацию. Пример подготовки данных для выгрузки в внешнюю систему:

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

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

"ВРЕМЕННЫЕ ТАБЛИЦЫ ВТ_ДанныеДляВыгрузки

// Шаг 1: Получаем данные о заказах

ВЫБРАТЬ

Заказ.Номер КАК НомерЗаказа,

Заказ.Дата КАК ДатаЗаказа,

Заказ.Контрагент КАК Контрагент,

ЗаказТовары.Номенклатура КАК Номенклатура,

ЗаказТовары.Количество КАК Количество,

ЗаказТовары.Цена КАК Цена

ПОМЕСТИТЬ ВТ_ДанныеДляВыгрузки

ИЗ

Документ.ЗаказПокупателя КАК Заказ

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказТовары

ПО Заказ.Ссылка = ЗаказТовары.Ссылка

ГДЕ

Заказ.Дата МЕЖДУ &НачалоПериода И &КонецПериода

И Заказ.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Оплачен)

// Шаг 2: Формируем JSON-структуру для выгрузки

ВЫБРАТЬ

ВТ_ДанныеДляВыгрузки.НомерЗаказа КАК НомерЗаказа,

JSONЗначение(

ВЫБРАТЬ

ВТ_ДанныеДляВыгрузки.ДатаЗаказа КАК Дата,

ВТ_ДанныеДляВыгрузки.Контрагент.Наименование КАК Контрагент,

ВЫБОР

КОГДА ВТ_ДанныеДляВыгрузки.Номенклатура.ЕстьФото

ТОГДА ВТ_ДанныеДляВыгрузки.Номенклатура.Фото.ПолучитьДвоичныеДанные()

ИНАЧЕ NULL

КОНЕЦ КАК ФотоТовара,

ВЫБРАТЬ ПЕРВЫЕ 100

ВТ_ДанныеДляВыгрузки.Номенклатура.Наименование КАК Товар,

ВТ_ДанныеДляВыгрузки.Количество КАК Количество,

ВТ_ДанныеДляВыгрузки.Цена КАК Цена

ИЗ

ВТ_ДанныеДляВыгрузки КАК ВТ_ДанныеДляВыгрузки2

ГДЕ

ВТ_ДанныеДляВыгрузки2.НомерЗаказа = ВТ_ДанныеДляВыгрузки.НомерЗаказа

ИЗ

ВТ_ДанныеДляВыгрузки КАК ВТ_ДанныеДляВыгрузки

ГДЕ

ВТ_ДанныеДляВыгрузки.НомерЗаказа = ВТ_ДанныеДляВыгрузки.НомерЗаказа

) КАК JSONДанные

ИЗ

ВТ_ДанныеДляВыгрузки КАК ВТ_ДанныеДляВыгрузки

ГДЕ

Истина

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

ВТ_ДанныеДляВыгрузки.НомерЗаказа";

Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата() - 30));

Запрос.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата()));

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

// Далее можно отправить JSONДанные во внешнюю систему

Для Каждого Строка Из Результат Цикл

ОтветСервера = ОтправитьЗапросНаСервер(Строка.JSONДанные);

КонецЦикла;

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

7. Отладка и анализ производительности

Отладка пакетных запросов может быть сложнее, чем обычных, из-за их комплексного характера. Вот ключевые инструменты и техники:

  • 🔍 Профайлер запросов (доступен с версии 8.3.20):
Запрос = Новый Запрос;

Запрос.ПрофайлерВключен = Истина;

Запрос.Текст = "ВЫБРАТЬ ...";

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

// Получаем данные профайлера

ПрофайлерДанные = Запрос.ПолучитьПрофайлерДанные();

Для Каждого Запись Из ПрофайлерДанные Цикл

Сообщить(СтрШаблон("Запрос: %1, Время: %2 мс",

Запись.ТекстЗапроса,

Запись.ВремяВыполнения));

КонецЦикла;

  • 📊 План выполнения запроса (в конфигураторе):

Откройте запрос в конфигураторе, нажмите F5 (или Текст → План выполнения), чтобы увидеть, как оптимизирует ваш запрос. Обратите внимание на:

  • 🔹 Full Table Scan (полное сканирование таблицы) — признак отсутствия индексов.
  • 🔹 Nested Loops — может указывать на неэффективные соединения.
  • 🔹 Cost (стоимость выполнения) — чем выше, тем хуже.
  • 🛠 Журнал регистрации:

Включите регистрацию событий Запрос и Транзакция в Администрирование → Журнал регистрации. Это поможет отследить долгие запросы и блокировки.

Пример анализа плана выполнения:


План выполнения:

1. TABLE SCAN (Справочник.Номенклатура) [Cost: 100]

2. INDEX SCAN (РегистрНакопления.ОстаткиТоваров) [Cost: 10]

3. NESTED LOOP JOIN [Cost: 1100]