При работе со сложными пакетными запросами в 1С:Предприятии разработчики часто сталкиваются с необходимостью получить данные не только из финальной выборки, но и из промежуточных временных таблиц. Это может потребоваться для отладки сложного алгоритма, логирования или для использования промежуточных расчетов в последующем программном коде на сервере 1С.
В этой статье мы подробно разберем, почему стандартный метод ВыполнитьПакет() не возвращает данные временных таблиц напрямую, рассмотрим различия в поведении платформы разных версий (1С 8.2 и 1С 8.3) и детально проанализируем четыре практических способа решения этой задачи с готовыми примерами кода.
Для начала проанализируем ситуацию и разберем внутренние механизмы работы платформы. Когда мы пишем пакетный запрос и используем конструкцию ПОМЕСТИТЬ вт_ИмяТаблицы, СУБД создает временную таблицу в своей системной базе данных (например, в базе tempdb для MS SQL Server). Сами данные физически остаются на сервере управления базами данных и не передаются на сервер приложений 1С.
Если мы вызываем стандартный метод Запрос.ВыполнитьПакет(), платформа возвращает массив объектов РезультатЗапроса. Однако для каждого шага пакета, где происходит создание временной таблицы (запрос с ключевым словом ПОМЕСТИТЬ), этот результат будет специфическим:
Выгрузить() вернет пустую таблицу значений с одной числовой колонкой.УНИЧТОЖИТЬ в соответствующем элементе массива результатов вернется значение Неопределено.Важное понимание механики: СУБД работает так в целях оптимизации производительности. Передача сотен тысяч строк из СУБД на сервер приложений 1С — это ресурсоемкая операция (сериализация, сетевой трафик, выделение памяти). Если данные нужны только для последующих соединений внутри СУБД, гонять их туда-обратно бессмысленно. Но что делать, если эти данные действительно понадобились нам в коде 1С?
Рассмотрим самый простой и надежный способ, который работает на абсолютно любой версии платформы 1С:Предприятие 8. Он заключается в том, чтобы явно принудить СУБД вернуть данные, добавив в пакет запроса еще один шаг с обычной выборкой из только что созданной временной таблицы.
Разберем этот метод по шагам на практическом примере:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ПОМЕСТИТЬ вт_Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// Шаг пакета для извлечения промежуточных данных
|ВЫБРАТЬ
| вт_Номенклатура.Ссылка КАК Ссылка
|ИЗ
| вт_Номенклатура КАК вт_Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// Финальный расчет
|ВЫБРАТЬ
| вт_Номенклатура.Ссылка КАК Ссылка,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Количество
|ИЗ
| вт_Номенклатура КАК вт_Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
| ПО ТоварыНаСкладахОстатки.Номенклатура = вт_Номенклатура.Ссылка";
РезультатыПакета = Запрос.ВыполнитьПакет();
// РезультатыПакета[0] — это результат создания ВТ (содержит только количество строк)
// РезультатыПакета[1] — это наш явный запрос выборки, содержащий реальные данные ВТ!
ТаблицаНоменклатуры = РезультатыПакета[1].Выгрузить();
// РезультатыПакета[2] — это итоговый результат всего пакета
ТаблицаИтогов = РезультатыПакета[2].Выгрузить();
Преимущества метода:
Недостатки метода:
ВЫБРАТЬ * ИЗ вт_ИмяТаблицы приведет к существенным затратам оперативной памяти сервера приложений 1С, даже если эти данные нужны только в редких ветках алгоритма.Если промежуточные данные нужны нам не всегда, а только при определенных условиях в коде, лучшим решением для платформы 1С 8.2 будет разделение одного большого пакетного запроса на несколько отдельных объектов Запрос, связанных через общий МенеджерВременныхТаблиц.
Посмотрим на пример реализации данного подхода:
// 1. Создаем менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;
// 2. Выполняем первый запрос, создающий временную таблицу
Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос1.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ПОМЕСТИТЬ вт_Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
// ВТ создается и сохраняется в Менеджере временных таблиц
Запрос1.Выполнить();
// 3. Теперь, когда в коде действительно потребовались данные этой ВТ:
ЗапросИзвлечения = Новый Запрос;
ЗапросИзвлечения.МенеджерВременныхТаблиц = МенеджерВТ;
ЗапросИзвлечения.Текст = "ВЫБРАТЬ Ссылка ИЗ вт_Номенклатура";
// Получаем реальные данные в таблицу значений
ТаблицаДанных = ЗапросИзвлечения.Выполнить().Выгрузить();
// 4. Продолжаем работу с основным пакетом, используя тот же менеджер
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос2.Текст =
"ВЫБРАТЬ
| вт_Номенклатура.Ссылка КАК Ссылка,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Количество
|ИЗ
| вт_Номенклатура КАК вт_Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
| ПО ТоварыНаСкладахОстатки.Номенклатура = вт_Номенклатура.Ссылка";
РезультатФинал = Запрос2.Выполнить().Выгрузить();
Преимущества метода:
Начиная с версии платформы 1С:Предприятие 8.3.8, у объекта Запрос появился штатный метод, созданный специально для решения описываемой проблемы — ВыполнитьПакетСПромежуточнымиДанными().
Этот метод работает точно так же, как и стандартный ВыполнитьПакет(), но с одним ключевым отличием: для всех шагов пакета, содержащих создание временных таблиц (оператор ПОМЕСТИТЬ), он принудительно выгружает реальное содержимое этих таблиц и возвращает их в виде полноценных объектов РезультатЗапроса со всеми строками и колонками.
Рассмотрим пример использования этого метода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ПОМЕСТИТЬ вт_Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| вт_Номенклатура.Ссылка КАК Ссылка,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Количество
|ИЗ
| вт_Номенклатура КАК вт_Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
| ПО ТоварыНаСкладахОстатки.Номенклатура = вт_Номенклатура.Ссылка";
// Используем современный метод платформы
МассивРезультатов = Запрос.ВыполнитьПакетСПромежуточнымиДанными();
// МассивРезультатов[0] содержит полноценную таблицу с данными вт_Номенклатура!
ТаблицаНоменклатуры = МассивРезультатов[0].Выгрузить();
// МассивРезультатов[1] содержит итоговый результат
ТаблицаИтогов = МассивРезультатов[1].Выгрузить();
Крайне важное предостережение по производительности: Несмотря на исключительное удобство метода ВыполнитьПакетСПромежуточнымиДанными(), использовать его в основном рабочем коде высоконагруженных систем без веских причин категорически не рекомендуется! При его вызове платформа 1С заставляет СУБД выгрузить в оперативную память сервера приложений абсолютно все временные таблицы пакета. Если они содержат сотни тысяч строк, это приведет к лавинообразному росту потребления памяти, нагрузке на сеть и резкому падению производительности всей системы. Используйте этот метод осознанно и только тогда, когда вам действительно нужны все промежуточные данные в коде 1С.
Еще одно мощное нововведение версии 8.3.8 — это появление коллекции Таблицы у объекта МенеджерВременныхТаблиц. Теперь мы можем обращаться к любой временной таблице по её имени прямо из кода, не меняя структуру пакетного запроса и не выгружая лишние таблицы.
Проанализируем пример кода:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ПОМЕСТИТЬ вт_Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
Запрос.Выполнить(); // Запрос выполнился, ВТ создана в менеджере
// Извлекаем данные конкретной ВТ по имени напрямую из менеджера!
ВременнаяТаблица = МенеджерВТ.Таблицы.Найти("вт_Номенклатура");
Если ВременнаяТаблица <> Неопределено Тогда
// Получаем данные в виде РезультатЗапроса и выгружаем
ТаблицаДанных = ВременнаяТаблица.ПолучитьДанные().Выгрузить();
КонецЕсли;
Этот способ объединяет в себе гибкость старого подхода с разделением запросов и избавляет от необходимости писать лишний код выборки внутри текста запроса. Он идеально подходит для точечного извлечения данных конкретных таблиц в промышленной эксплуатации.
Для глубокого понимания важно осознавать, где именно хранятся данные. Временная таблица в менеджере временных таблиц (ВТ) — это объект базы данных на стороне СУБД. Когда мы пишем запрос, СУБД создает физическую или виртуальную таблицу на своих дисках или в оперативной памяти. Доступ к этой таблице осуществляется чрезвычайно быстро, так как СУБД оптимизирует планы выполнения запросов.
Когда мы выгружаем данные в таблицу значений на сторону 1С (используя метод Выгрузить()), происходят следующие процессы:
ТаблицаЗначений.rphost.Если мы делаем это необоснованно в цикле или для больших объемов данных, это гарантированно приведет к деградации производительности всей системы. Именно поэтому в продуктивном коде всегда следует отдавать приоритет работе внутри СУБД (через менеджер ВТ) и минимизировать выгрузку промежуточных результатов на сервер приложений.
Каждый 1С-разработчик сталкивается с ситуацией, когда сложный запрос выдает неверный результат, и нужно понять, на каком именно шаге (в какой временной таблице) закралась ошибка. Благодаря современным возможностям платформы 8.3.8+, изменять исходный код конфигурации для отладки больше нет необходимости.
Если вы остановились на точке останова в конфигураторе сразу после выполнения запроса, откройте окно «Табло» (сочетание клавиш Shift + F9) и используйте следующие выражения для просмотра содержимого временных таблиц в реальном времени:
ВыполнитьПакет(), но хотите прямо сейчас в отладчике увидеть промежуточные данные, вычислите выражение:
Запрос.ВыполнитьПакетСПромежуточнымиДанными()[0].Выгрузить()
*(где вместо 0 укажите индекс интересующей вас временной таблицы в пакете).*
МенеджерВременныхТаблиц, вы можете посмотреть данные по имени таблицы, не задумываясь об индексах:
Запрос.МенеджерВременныхТаблиц.Таблицы.Найти("вт_Номенклатура").ПолучитьДанные().Выгрузить()
Эти простые приемы сэкономят вам часы работы, позволяя мгновенно визуализировать состояние временных таблиц непосредственно в процессе выполнения кода и быстро локализовать любые логические ошибки в алгоритмах.