При работе с платформой 1С:Предприятие 8.3 разработчики часто сталкиваются с задачей динамического формирования запросов, когда часть данных для запроса поступает не из базы данных, а из внешней ТаблицыЗначений. Рассмотрим, как можно решить эту задачу, используя как устаревший, но всё ещё встречающийся в работе инструмент "Построитель запроса", так и современную "Систему Компоновки Данных" (СКД).
Основной вопрос, который задают разработчики, заключается в том, можно ли "красиво" передать внешнюю таблицу в построитель запросов или СКД таким образом, чтобы она могла быть использована как временная таблица или параметр, не игнорируя при этом основной текст запроса или его динамические возможности.
Прежде всего, важно понимать, что объект ПостроительЗапроса (часто входящий в состав ПостроительОтчета) является инструментом, который в последние годы не развивается. На смену ему пришла гораздо более мощная и гибкая СистемаКомпоновкиДанных (СКД).
Прямая передача ТаблицыЗначений в ПостроительЗапроса как параметра не поддерживается напрямую. Это означает, что вы не сможете просто установить параметр типа ТаблицаЗначений для построителя, как это делается для обычного объекта Запрос.
Тем не менее, ПостроительЗапроса может быть использован для некоторых задач, например, для выполнения гибкого отбора в уже существующей ТаблицеЗначений на стороне 1С, без необходимости обращаться к СУБД. В этом случае вы устанавливаете ТаблицуЗначений как ИсточникДанных для построителя. Однако, при таком подходе текст запроса построителя будет проигнорирован, и все настройки построителя будут применяться к переданной ТаблицеЗначений, а не к запросу к базе данных.
Рассмотрим пример использования временной таблицы *непосредственно в тексте запроса* построителя. Это не передача *внешней* таблицы, а создание и использование временной таблицы *внутри* одного и того же запроса, который будет выполняться построителем. Это фундаментальный аспект работы с запросами в 1С, позволяющий структурировать сложные выборки.
В этом примере мы сначала помещаем данные из справочника во временную таблицу, а затем используем ее в основном запросе:
ТекстЗапроса = "ВЫБРАТЬ
| Пользователи.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Менеджеры
|ИЗ
| Справочник.Пользователи КАК Пользователи
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ПродажиОбороты.Менеджер КАК Менеджер,
| ПродажиОбороты.Номенклатура КАК Номенклатура
|ИЗ
| ВТ_Менеджеры КАК ВТ_Менеджеры
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
| ПО ВТ_Менеджеры.Ссылка = ПродажиОбороты.Менеджер";
ПЗ = Новый ПостроительЗапроса();
ПЗ.Текст = ТекстЗапроса;
ТЗ = ПЗ.Результат.Выгрузить();
Данный подход работает и позволяет использовать временные таблицы в запросах, передаваемых в ПостроительЗапроса, но это не решает проблему передачи *внешней* ТаблицыЗначений.
Поскольку прямая передача ТаблицыЗначений невозможна, нам приходится прибегать к так называемым "финтам ушами" или обходным путям. Этот метод заключается в следующем:
ПостроительЗапроса, мы используем специальную "заглушку" вместо фактического имени внешней временной таблицы или параметра. Например, это может быть не существующее имя временной таблицы или параметр, который будет позже заменен.Запрос: После того как ПостроительЗапроса настроен, и его настройки применены, мы получаем объект Запрос с уже скомпонованным текстом с помощью метода Построитель.ПолучитьЗапрос().Запрос, находим нашу "заглушку" и заменяем её на корректный синтаксис для параметра ТаблицаЗначений (например, &ВнешняяТЗ).ТаблицуЗначений в качестве параметра для объекта Запрос с помощью метода Запрос.УстановитьПараметр("ВнешняяТЗ", ВашаТаблицаЗначений).Запрос, и он будет работать с переданной ТаблицейЗначений.Критически важно: типизация колонок ТаблицыЗначений. При создании ТаблицыЗначений, которую мы собираемся передавать в запрос, необходимо явно типизировать все колонки с помощью метода ТаблицаЗначений.Колонки.Добавить("ИмяКолонки", Новый ОписаниеТипов(...)). Если этого не сделать, платформа может некорректно определить типы данных при передаче в запрос, что приведет к ошибкам выполнения. Например, для колонок типа "Ссылка" следует указать точный тип справочника или документа.
Пример реализации обходного пути:
// 1. Подготавливаем внешнюю таблицу значений
ВнешняяТЗ = Новый ТаблицаЗначений();
ВнешняяТЗ.Колонки.Добавить("Менеджер", Новый ОписаниеТипов("СправочникСсылка.Пользователи"));
ВнешняяТЗ.Колонки.Добавить("СуммаПродаж", Новый ОписаниеТипов("Число"));
// Добавляем тестовые данные
НоваяСтрока = ВнешняяТЗ.Добавить();
НоваяСтрока.Менеджер = Справочники.Пользователи.НайтиПоКоду("000000001"); // Пример ссылки на пользователя
НоваяСтрока.СуммаПродаж = 1000;
НоваяСтрока = ВнешняяТЗ.Добавить();
НоваяСтрока.Менеджер = Справочники.Пользователи.НайтиПоКоду("000000002");
НоваяСтрока.СуммаПродаж = 2500;
// 2. Создаем текст запроса для Построителя с заглушкой
// Вместо &ВнешняяТЗ мы могли бы использовать, например,
// ПОМЕСТИТЬ ВТ_ЗАГЛУШКА ИЗ Справочник.НесуществующийСправочник
// и потом заменить ее на &ВнешняяТЗ
ТекстЗапросаПостроителя = "ВЫБРАТЬ
| Заглушка.Менеджер КАК МенеджерВнешний,
| Заглушка.СуммаПродаж КАК СуммаПродажВнешний
|ИЗ
| ВРЕМЕННАЯ_ТАБЛИЦА_ЗАГЛУШКА КАК Заглушка // Это наша заглушка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ПродажиОбороты.Менеджер КАК Менеджер,
| ПродажиОбороты.Номенклатура КАК Номенклатура,
| ЗапросИзПостроителя.СуммаПродажВнешний
|ИЗ
| РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ЗапросИзПостроителя КАК ЗапросИзПостроителя
| ПО ПродажиОбороты.Менеджер = ЗапросИзПостроителя.МенеджерВнешний";
// 3. Создаем ПостроительЗапроса и устанавливаем текст
ПЗ = Новый ПостроительЗапроса();
ПЗ.Текст = ТекстЗапросаПостроителя;
// 4. Получаем объект Запрос
Запрос = ПЗ.ПолучитьЗапрос();
// 5. Модифицируем текст запроса, заменяя заглушку на реальный параметр
// Заглушка: ВРЕМЕННАЯ_ТАБЛИЦА_ЗАГЛУШКА
// Заменяем на: &ВнешняяТЗ
Запрос.Текст = СтрЗаменить(Запрос.Текст, "ВРЕМЕННАЯ_ТАБЛИЦА_ЗАГЛУШКА", "&ВнешняяТЗ");
// 6. Устанавливаем параметр
Запрос.УстановитьПараметр("ВнешняяТЗ", ВнешняяТЗ);
// 7. Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить("Менеджер: " + Выборка.Менеджер + ", Номенклатура: " + Выборка.Номенклатура +
", СуммаПродажИзВнешнейТЗ: " + Выборка.СуммаПродажВнешний);
КонецЦикла;
Этот подход работает, но является довольно громоздким и требует ручной модификации текста запроса, что делает его "неидеальным", как и отмечал автор исходного сообщения.
Как уже было сказано, СистемаКомпоновкиДанных (СКД) является основным и наиболее функциональным инструментом для построения отчетов и работы с данными в современных конфигурациях 1С. Динамические списки в управляемых формах, многие типовые отчеты — все они основаны на механизме СКД.
С появлением платформы 1С:Предприятие версии 8.3.17, была существенно улучшена поддержка работы с временными таблицами в СКД. Теперь появилась возможность напрямую передавать внешние таблицы (включая ТаблицыЗначений) в СКД через МенеджерВременныхТаблиц. Это наиболее "красивый" и современный способ интеграции внешних данных в запросы СКД. Для упрощения программного формирования структуры схемы компоновки данных можно использовать общий модуль-обёртку для быстрого создания объектов СКД.
Рассмотрим подробнее, как работает этот механизм:
МенеджерВременныхТаблиц: Это специальный объект, который позволяет управлять временными таблицами в контексте выполнения запросов.ТаблицыЗначений: Мы создаем обычный объект Запрос, устанавливаем ему нашу ТаблицуЗначений как параметр и выполняем его с помощью МенеджерВременныхТаблиц. Этот запрос должен поместить данные из нашей ТаблицыЗначений во временную таблицу в контексте менеджера.МенеджерВременныхТаблиц в ПроцессорКомпоновкиДанных: После того как внешняя таблица помещена во временную таблицу, мы передаем объект МенеджерВременныхТаблиц в ПроцессорКомпоновкиДанных.Посмотрим на пример:
// 1. Подготавливаем внешнюю таблицу значений (аналогично предыдущему примеру)
ВнешняяТЗ = Новый ТаблицаЗначений();
ВнешняяТЗ.Колонки.Добавить("Менеджер", Новый ОписаниеТипов("СправочникСсылка.Пользователи"));
ВнешняяТЗ.Колонки.Добавить("СуммаПродаж", Новый ОписаниеТипов("Число"));
НоваяСтрока = ВнешняяТЗ.Добавить();
НоваяСтрока.Менеджер = Справочники.Пользователи.НайтиПоКоду("000000001");
НоваяСтрока.СуммаПродаж = 1000;
НоваяСтрока = ВнешняяТЗ.Добавить();
НоваяСтрока.Менеджер = Справочники.Пользователи.НайтиПоКоду("000000002");
НоваяСтрока.СуммаПродаж = 2500;
// 2. Создаем МенеджерВременныхТаблиц
МВТ = Новый МенеджерВременныхТаблиц();
// 3. Помещаем ВнешнююТЗ во временную таблицу с помощью объекта Запрос
ЗапросПомещенияВТ = Новый Запрос();
ЗапросПомещенияВТ.МенеджерВременныхТаблиц = МВТ;
ЗапросПомещенияВТ.Текст = "ВЫБРАТЬ
| ВнешняяТаблица.Менеджер КАК Менеджер,
| ВнешняяТаблица.СуммаПродаж КАК СуммаПродаж
|ПОМЕСТИТЬ ВТ_ВнешниеДанные
|ИЗ
| &ВнешняяТЗ КАК ВнешняяТаблица";
ЗапросПомещенияВТ.УстановитьПараметр("ВнешняяТЗ", ВнешняяТЗ);
ЗапросПомещенияВТ.Выполнить();
// 4. Создаем схему компоновки данных (или получаем из внешнего файла/объекта)
СхемаКомпоновкиДанных = Новый СхемаКомпоновкиДанных();
// Допустим, у нас уже есть набор данных в СКД, который обращается к ВТ_ВнешниеДанные
// Пример текста запроса в наборе данных СКД:
// ВЫБРАТЬ
// ПродажиОбороты.Менеджер,
// ПродажиОбороты.Номенклатура,
// ВТ_ВнешниеДанные.СуммаПродаж
// ИЗ
// РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
// ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ВнешниеДанные КАК ВТ_ВнешниеДанные
// ПО ПродажиОбороты.Менеджер = ВТ_ВнешниеДанные.Менеджер
// Мы можем динамически добавить набор данных, но обычно СКД строится заранее.
// Для примера, создадим простую схему, чтобы продемонстрировать МВТ.
// Создаем набор данных-запрос
НаборДанныхЗапрос = СхемаКомпоновкиДанных.НаборыДанных.Добавить(Тип("НаборДанныхЗапрос"));
НаборДанныхЗапрос.Имя = "НаборДанных1";
НаборДанныхЗапрос.Запрос = "ВЫБРАТЬ
| ПродажиОбороты.Менеджер,
| ПродажиОбороты.Номенклатура,
| ВТ_ВнешниеДанные.СуммаПродаж
|ИЗ
| РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВнешниеДанные КАК ВТ_ВнешниеДанные
| ПО ПродажиОбороты.Менеджер = ВТ_ВнешниеДанные.Менеджер";
// 5. Создаем компоновщик макета и процессор компоновки данных
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных();
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Новый НастройкиКомпоновкиДанных());
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных();
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, , МВТ); // Передаем МВТ!
// 6. Выполняем компоновку и выводим результат
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент();
ТабличныйДокумент = Новый ТабличныйДокумент();
ПроцессорВывода.УстановитьДокумент(ТабличныйДокумент);
ПроцессорВывода.НачатьВывод();
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновкиДанных.Следующий();
Если ЭлементРезультата = Неопределено Тогда
Прервать;
КонецЕсли;
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
ТабличныйДокумент.Показать();
Также в СКД существует концепция "набора данных объект". Этот механизм позволяет СКД строить отчеты, используя в качестве источника данных не запрос к базе данных, а произвольный объект 1С, например, ту же ТаблицуЗначений. Однако, важно понимать, что к такому набору данных нельзя напрямую применить запрос. "Набор данных объект" используется на более поздних этапах конвейера компоновки данных, когда данные уже получены и готовы для дальнейшей обработки (группировка, отбор, расчет итогов).
Этот подход полезен, если вы хотите просто отобразить или сгруппировать уже готовую ТаблицуЗначений средствами СКД, но не для того, чтобы объединить ее с данными базы данных *через запрос*. Для решения схожих задач фильтрации подойдёт обработка-пример с отбором СКД по табличной части или таблице значений.
Для стандартного объекта Запрос, без использования ПостроителяЗапроса или СКД, передача ТаблицыЗначений в качестве параметра и помещение ее во временную таблицу в рамках пакетного запроса является стандартной и рекомендуемой практикой. Этот подход позволяет избежать выполнения запросов в цикле и значительно оптимизировать производительность.
Мы можем передать ТаблицуЗначений как параметр запроса, а затем в первом блоке пакетного запроса поместить ее во временную таблицу, чтобы использовать в последующих блоках:
Запрос = Новый Запрос();
Запрос.Текст = "ВЫБРАТЬ
| ПараметрТЗ.Менеджер КАК Менеджер,
| ПараметрТЗ.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Параметр
|ИЗ
| &ВнешняяТЗ КАК ПараметрТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ПродажиОбороты.Менеджер,
| ПродажиОбороты.Номенклатура,
| ВТ_Параметр.Сумма
|ИЗ
| РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Параметр КАК ВТ_Параметр
| ПО ПродажиОбороты.Менеджер = ВТ_Параметр.Менеджер";
// Подготовка ВнешняяТЗ аналогична предыдущим примерам
ВнешняяТЗ = Новый ТаблицаЗначений();
ВнешняяТЗ.Колонки.Добавить("Менеджер", Новый ОписаниеТипов("СправочникСсылка.Пользователи"));
ВнешняяТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
// ... заполнение ВнешняяТЗ ...
Запрос.УстановитьПараметр("ВнешняяТЗ", ВнешняяТЗ);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка выборки
КонецЦикла;
Этот способ является наиболее прямым и эффективным для обычных запросов, не связанных с динамическим построением отчетов через ПостроительЗапроса или СКД.
Независимо от выбранного метода передачи ТаблицыЗначений в запрос (будь то через обходной путь в ПостроителеЗапроса, МенеджерВременныхТаблиц в СКД или обычный пакетный запрос), важно помнить об особенностях обращения к реквизитам ссылочных типов.
Если в вашей ТаблицеЗначений есть колонка, содержащая ссылки (например, на элементы справочников или документов), и вы хотите в запросе обращаться к реквизитам этих ссылок (например, ВТ.Менеджер.Наименование или ВТ.Контрагент.Родитель), то эти реквизиты должны быть:
ТаблицыЗначений (например, ВЫБРАТЬ ТЗ.Менеджер, ТЗ.Менеджер.Наименование КАК НаименованиеМенеджера ПОМЕСТИТЬ ВТ_Данные).ТаблицеЗначений должна быть корректно типизирована с помощью ОписаниеТипов, чтобы платформа могла построить правильный запрос к базе данных. Однако, прямой доступ к "точкам" (.) через временную таблицу, сформированную из ТаблицыЗначений, может быть ограничен, если вы не используете МенеджерВременныхТаблиц с его расширенными возможностями. Наиболее надежным способом является явное извлечение нужных полей в запросе, формирующем временную таблицу, или соединение с соответствующими справочниками/документами в основном запросе.Особенно это актуально для объектов, которые требуют виртуальных таблиц (например, Справочник.Контрагенты.Представление, или доступ к реквизитам через иерархию). Мы должны быть уверены, что все необходимые поля доступны во временной таблице или через явные соединения.
Мы выяснили, что для ПостроителяЗапроса прямая передача ТаблицыЗначений как параметра невозможна. Единственный рабочий, но трудоемкий подход — это "финт ушами" с программной заменой заглушки и установкой параметра для объекта Запрос, полученного из построителя.
Для современных версий платформы 1С (начиная с 8.3.17) и для динамических отчетов наиболее "красивым" и функциональным способом является использование СистемыКомпоновкиДанных (СКД) в сочетании с МенеджеромВременныхТаблиц. Этот механизм предоставляет надежный и производительный способ интеграции внешних данных в сложные запросы отчетов.
Для обычных, нединамических запросов, напрямую через объект Запрос, использование пакетных запросов с параметром типа ТаблицаЗначений является стандартным и наиболее эффективным решением. Во всех случаях крайне важно правильно типизировать колонки передаваемых ТаблицЗначений.
При принятии решения о том, какой инструмент использовать, мы должны ориентироваться на специфику задачи: если требуется максимальная гибкость и динамичность в построении отчетов, то СКД — это наш выбор. Если же задача ограничивается лишь программным формированием запроса с участием внешней таблицы, то объект Запрос с пакетными запросами будет достаточен. ПостроительЗапроса стоит использовать только в случаях, когда его применение обусловлено исторически или является частью более специфического, нетипичного решения, требующего его динамических возможностей в части настройки отборов и полей, но с учетом описанных ограничений.