Как выполнить запрос с разными параметрами и объединить результаты в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Разберем, почему многократный вызов метода Выполнить() в цикле или подряд с подменой параметров — не самое оптимальное решение, и выясним, как правильно решить эту задачу на уровне встроенного языка и языка запросов 1С.

Особенности последовательного выполнения одного запроса

Технически платформа позволяет многократно вызывать метод Выполнить() у одного экземпляра объекта Запрос, последовательно меняя параметры через УстановитьПараметр():


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Номенклатура, КоличествоОстаток ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(,&Склад) КАК Т";

Запрос.УстановитьПараметр("Склад", Склад1);
Результат1 = Запрос.Выполнить();

Запрос.УстановитьПараметр("Склад", Склад2);
Результат2 = Запрос.Выполнить();

Однако каждый отдельный вызов Выполнить() инициирует отдельное обращение к СУБД (round-trip). Кроме того, результаты возвращаются в виде двух независимых объектов РезультатЗапроса, которые невозможно напрямую связать средствами языка запросов без дополнительных манипуляций. Проанализируем наиболее эффективные подходы для объединения и выборочной группировки таких данных.

Способ 1. Пакетный запрос с временными таблицами и объединением

Наиболее правильным с точки зрения производительности и возможностей агрегации является объединение выборок непосредственно внутри одного текста запроса с помещением во временную таблицу (ВТ). Это позволяет выполнить всю работу за одно обращение к базе данных и сразу же сгруппировать строки по нужным полям.

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|   1 КАК Вариант,
|   Т.Номенклатура КАК Номенклатура,
|   Т.КоличествоОстаток КАК Количество
|ПОМЕСТИТЬ ВТ_ОбщиеДанные
|ИЗ
|   РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад1) КАК Т
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
|   2 КАК Вариант,
|   Т.Номенклатура КАК Номенклатура,
|   Т.КоличествоОстаток КАК Количество
|ИЗ
|   РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад2) КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|   ВТ.Номенклатура КАК Номенклатура,
|   СУММА(ВТ.Количество) КАК КоличествоВсего,
|   СУММА(ВЫБОР КОГДА ВТ.Вариант = 1 ТОГДА ВТ.Количество ИНАЧЕ 0 КОНЕЦ) КАК КоличествоСклад1,
|   СУММА(ВЫБОР КОГДА ВТ.Вариант = 2 ТОГДА ВТ.Количество ИНАЧЕ 0 КОНЕЦ) КАК КоличествоСклад2
|ИЗ
|   ВТ_ОбщиеДанные КАК ВТ
|СГРУППИРОВАТЬ ПО
|   ВТ.Номенклатура";

Запрос.УстановитьПараметр("Склад1", Склад1);
Запрос.УстановитьПараметр("Склад2", Склад2);

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

Обратим внимание на искусственное поле-маркер (1 КАК Вариант и 2 КАК Вариант). Оно решает проблему, когда не все поля должны смешиваться при группировке: с помощью конструкции ВЫБОР КОГДА мы можем избирательно суммировать данные для первого или второго набора параметров, сохранив общую группировку по ключевым полям.

Способ 2. Использование условия с проверкой вхождения в список

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

Разберем этот вариант по шагам:

  1. Формируем массив параметров, например, список складов, подразделений или периодов.
  2. Передаем этот массив в качестве одного параметра запроса.
  3. В самом запросе выбираем поле отбора, чтобы сохранить признак принадлежности к конкретному значению.

СписокСкладов = Новый Массив;
СписокСкладов.Добавить(Склад1);
СписокСкладов.Добавить(Склад2);

Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|   Т.Склад КАК Склад,
|   Т.Номенклатура КАК Номенклатура,
|   СУММА(Т.КоличествоОстаток) КАК Количество
|ИЗ
|   РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад В (&СписокСкладов)) КАК Т
|СГРУППИРОВАТЬ ПО
|   Т.Склад,
|   Т.Номенклатура";

Запрос.УстановитьПараметр("СписокСкладов", СписокСкладов);
Результат = Запрос.Выполнить();

Если при этом требуется частично сгруппировать поля, а частично вывести раздельно, можно использовать секцию ИТОГИ вместо СГРУППИРОВАТЬ ПО. При обходе выборки с параметром ОбходРезультатаЗапроса.ПоГруппировкам платформа предоставит доступ как к общим суммам, так и к детальным записям по каждому складу отдельно.

Способ 3. Использование Менеджера временных таблиц

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

Посмотрим на пример организации такого кода:


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

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

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

// Результирующий запрос с объединением или соединением
ЗапросИтог = Новый Запрос;
ЗапросИтог.МенеджерВременныхТаблиц = МенеджерВТ;
ЗапросИтог.Текст = 
"ВЫБРАТЬ
|   ЕСТЬNULL(ВТ1.Номенклатура, ВТ2.Номенклатура) КАК Номенклатура,
|   ЕСТЬNULL(ВТ1.КоличествоПараметр1, 0) КАК Количество1,
|   ЕСТЬNULL(ВТ2.КоличествоПараметр2, 0) КАК Количество2
|ИЗ
|   ВТ_Первая КАК ВТ1
|   ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Вторая КАК ВТ2
|   ПО ВТ1.Номенклатура = ВТ2.Номенклатура";

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

// Закрываем менеджер для освобождения ресурсов
МенеджерВТ.Закрыть();

Этот способ позволяет гибко комбинировать результаты разных вызовов с помощью конструкций ОБЪЕДИНИТЬ или различных типов соединений (ЛЕВОЕ СОЕДИНЕНИЕ, ПОЛНОЕ СОЕДИНЕНИЕ), не перегружая память сервера промежуточными объектами встроенного языка.

Способ 4. Обработка результатов в памяти через ТаблицуЗначений

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

Разберем реализацию этого метода:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|   Т.Номенклатура КАК Номенклатура,
|   Т.КоличествоОстаток КАК Количество
|ИЗ
|   РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Т";

Запрос.УстановитьПараметр("Склад", Склад1);
Таблица1 = Запрос.Выполнить().Выгрузить();

Запрос.УстановитьПараметр("Склад", Склад2);
Таблица2 = Запрос.Выполнить().Выгрузить();

// Заполняем первую таблицу строками второй
Для Каждого Строка Из Таблица2 Цикл
    ЗаполнитьЗначенияСвойств(Таблица1.Добавить(), Строка);
КонецЦикла;

// Сворачиваем строки: первый параметр — колонки группировки, второй — агрегируемые суммы
Таблица1.Свернуть("Номенклатура", "Количество");

Важные ограничения данного метода:

  1. Метод Свернуть() поддерживает только суммирование числовых колонок. Вычислить максимум, минимум или количество различных значений с его помощью нельзя.
  2. Работа с объектом ТаблицаЗначений возможна только в серверном контексте — на тонком клиенте этот тип данных недоступен.
  3. При больших объемах данных копирование строк в цикле и последующая свертка в оперативной памяти уступают по скорости группировке на уровне СУБД.

Таким образом, для большинства практических сценариев наиболее надежным и быстрым вариантом остается пакетный запрос с временными таблицами и конструкцией ОБЪЕДИНИТЬ ВСЕ, дополненный полями-маркерами для избирательной группировки.

← На главную