В практике разработки на платформе 1С:Предприятие нередко возникает задача: требуется получить данные по одной и той же схеме или таблице, но при разных значениях параметров, а затем сопоставить, сгруппировать или объединить полученные данные в единую результирующую выборку. При этом часть полей должна агрегироваться, а часть — выводиться раздельно.
Разберем, почему многократный вызов метода Выполнить() в цикле или подряд с подменой параметров — не самое оптимальное решение, и выясним, как правильно решить эту задачу на уровне встроенного языка и языка запросов 1С.
Технически платформа позволяет многократно вызывать метод Выполнить() у одного экземпляра объекта Запрос, последовательно меняя параметры через УстановитьПараметр():
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Номенклатура, КоличествоОстаток ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(,&Склад) КАК Т";
Запрос.УстановитьПараметр("Склад", Склад1);
Результат1 = Запрос.Выполнить();
Запрос.УстановитьПараметр("Склад", Склад2);
Результат2 = Запрос.Выполнить();
Однако каждый отдельный вызов Выполнить() инициирует отдельное обращение к СУБД (round-trip). Кроме того, результаты возвращаются в виде двух независимых объектов РезультатЗапроса, которые невозможно напрямую связать средствами языка запросов без дополнительных манипуляций. Проанализируем наиболее эффективные подходы для объединения и выборочной группировки таких данных.
Наиболее правильным с точки зрения производительности и возможностей агрегации является объединение выборок непосредственно внутри одного текста запроса с помещением во временную таблицу (ВТ). Это позволяет выполнить всю работу за одно обращение к базе данных и сразу же сгруппировать строки по нужным полям.
Рассмотрим, как выглядит текст такого запроса, когда требуется получить данные по двум разным параметрам и объединить их:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| 1 КАК Вариант,
| Т.Номенклатура КАК Номенклатура,
| Т.КоличествоОстаток КАК Количество
|ПОМЕСТИТЬ ВТ_ОбщиеДанные
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад1) КАК Т
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| 2 КАК Вариант,
| Т.Номенклатура КАК Номенклатура,
| Т.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад2) КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| СУММА(ВТ.Количество) КАК КоличествоВсего,
| СУММА(ВЫБОР КОГДА ВТ.Вариант = 1 ТОГДА ВТ.Количество ИНАЧЕ 0 КОНЕЦ) КАК КоличествоСклад1,
| СУММА(ВЫБОР КОГДА ВТ.Вариант = 2 ТОГДА ВТ.Количество ИНАЧЕ 0 КОНЕЦ) КАК КоличествоСклад2
|ИЗ
| ВТ_ОбщиеДанные КАК ВТ
|СГРУППИРОВАТЬ ПО
| ВТ.Номенклатура";
Запрос.УстановитьПараметр("Склад1", Склад1);
Запрос.УстановитьПараметр("Склад2", Склад2);
Результат = Запрос.Выполнить();
Обратим внимание на искусственное поле-маркер (1 КАК Вариант и 2 КАК Вариант). Оно решает проблему, когда не все поля должны смешиваться при группировке: с помощью конструкции ВЫБОР КОГДА мы можем избирательно суммировать данные для первого или второго набора параметров, сохранив общую группировку по ключевым полям.
Если структура выбираемых данных для обоих параметров полностью идентична, а условия отбора накладываются на одно и то же поле, можно объединить параметры в массив или список значений и передать их в условие через оператор В.
Разберем этот вариант по шагам:
СписокСкладов = Новый Массив;
СписокСкладов.Добавить(Склад1);
СписокСкладов.Добавить(Склад2);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Склад КАК Склад,
| Т.Номенклатура КАК Номенклатура,
| СУММА(Т.КоличествоОстаток) КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад В (&СписокСкладов)) КАК Т
|СГРУППИРОВАТЬ ПО
| Т.Склад,
| Т.Номенклатура";
Запрос.УстановитьПараметр("СписокСкладов", СписокСкладов);
Результат = Запрос.Выполнить();
Если при этом требуется частично сгруппировать поля, а частично вывести раздельно, можно использовать секцию ИТОГИ вместо СГРУППИРОВАТЬ ПО. При обходе выборки с параметром ОбходРезультатаЗапроса.ПоГруппировкам платформа предоставит доступ как к общим суммам, так и к детальным записям по каждому складу отдельно.
Когда текст запроса сложный или формируется динамически в разных процедурах модуля, последовательные вызовы можно связать через МенеджерВременныхТаблиц. В этом случае таблицы сохраняются в базе данных на время жизни объекта менеджера.
Посмотрим на пример организации такого кода:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
// Первый запрос
Запрос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.Номенклатура";
ИтоговыйРезультат = ЗапросИтог.Выполнить();
// Закрываем менеджер для освобождения ресурсов
МенеджерВТ.Закрыть();
Этот способ позволяет гибко комбинировать результаты разных вызовов с помощью конструкций ОБЪЕДИНИТЬ или различных типов соединений (ЛЕВОЕ СОЕДИНЕНИЕ, ПОЛНОЕ СОЕДИНЕНИЕ), не перегружая память сервера промежуточными объектами встроенного языка.
Если обращаться к СУБД повторно нежелательно или логика объединения слишком сложна для языка запросов, результаты можно объединить в оперативной памяти с помощью метода Свернуть() коллекции ТаблицаЗначений.
Разберем реализацию этого метода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Т";
Запрос.УстановитьПараметр("Склад", Склад1);
Таблица1 = Запрос.Выполнить().Выгрузить();
Запрос.УстановитьПараметр("Склад", Склад2);
Таблица2 = Запрос.Выполнить().Выгрузить();
// Заполняем первую таблицу строками второй
Для Каждого Строка Из Таблица2 Цикл
ЗаполнитьЗначенияСвойств(Таблица1.Добавить(), Строка);
КонецЦикла;
// Сворачиваем строки: первый параметр — колонки группировки, второй — агрегируемые суммы
Таблица1.Свернуть("Номенклатура", "Количество");
Важные ограничения данного метода:
Свернуть() поддерживает только суммирование числовых колонок. Вычислить максимум, минимум или количество различных значений с его помощью нельзя.ТаблицаЗначений возможна только в серверном контексте — на тонком клиенте этот тип данных недоступен.Таким образом, для большинства практических сценариев наиболее надежным и быстрым вариантом остается пакетный запрос с временными таблицами и конструкцией ОБЪЕДИНИТЬ ВСЕ, дополненный полями-маркерами для избирательной группировки.