В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда данные для итогового отчета или обработки нужно собрать из разных источников. Иногда это два разных запроса к базе данных, а иногда — сочетание данных из базы и внешней таблицы значений, сформированной программно. Начинающие разработчики часто пытаются «сложить» объекты типа ВыборкаИзРезультатаЗапроса напрямую, но сталкиваются с тем, что у этих объектов нет методов для слияния.
В этой статье мы подробно разберем, почему нельзя соединить выборки напрямую, и как правильно использовать объект МенеджерВременныхТаблиц для решения этой задачи.
Давайте проанализируем природу объектов 1С. Объект ВыборкаИзРезультатаЗапроса — это курсор, который позволяет последовательно обходить строки, полученные из базы данных. Он предназначен для чтения, а не для манипуляции структурой данных. Попытка использовать методы вроде ДополнитьТаблицу() или оператор «+» к выборкам приведет к ошибке, так как эти инструменты работают с коллекциями в памяти (например, ТаблицаЗначений), а не с потоками данных из СУБД.
Чтобы соединить данные, нам нужно перенести их в среду, где СУБД сможет выполнить операцию JOIN (соединение). Именно для этого служит МенеджерВременныхТаблиц.
Рассмотрим ситуацию: у нас есть два независимых запроса. В первом мы получаем цены номенклатуры, во втором — фактические суммы из регистров. Нам нужно объединить их по ключевому полю Номенклатура.
Для этого мы создадим один экземпляр МенеджерВременныхТаблиц и будем передавать его разным запросам. Разберем по шагам, как это работает в коде:
// 1. Создаем менеджер временных таблиц
МТ = Новый МенеджерВременныхТаблиц;
// 2. Первый запрос: помещаем данные во временную таблицу ВТ1
Запрос1 = Новый Запрос;
Запрос1.МенеджерВременныхТаблиц = МТ;
Запрос1.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_Цены
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены";
Запрос1.УстановитьПараметр("Период", ТекущаяДата());
Запрос1.Выполнить();
// 3. Второй запрос: помещаем данные во временную таблицу ВТ2
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МТ;
Запрос2.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.СуммаОборот КАК Сумма
|ПОМЕСТИТЬ ВТ_Продажи
|ИЗ
| РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , ) КАК Продажи";
Запрос2.УстановитьПараметр("Начало", НачалоМесяца(ТекущаяДата()));
Запрос2.УстановитьПараметр("Конец", КонецМесяца(ТекущаяДата()));
Запрос2.Выполнить();
// 4. Третий запрос: соединяем временные таблицы, хранящиеся в МТ
ЗапросИтоговый = Новый Запрос;
ЗапросИтоговый.МенеджерВременныхТаблиц = МТ;
ЗапросИтоговый.Текст =
"ВЫБРАТЬ
| ВТ_Цены.Номенклатура,
| ВТ_Цены.Цена,
| ЕСТЬNULL(ВТ_Продажи.Сумма, 0) КАК Сумма
|ИЗ
| ВТ_Цены КАК ВТ_Цены
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Продажи КАК ВТ_Продажи
| ПО ВТ_Цены.Номенклатура = ВТ_Продажи.Номенклатура";
Результат = ЗапросИтоговый.Выполнить();
Выборка = Результат.Выбрать();
Важный нюанс: временные таблицы ВТ_Цены и ВТ_Продажи физически существуют в базе данных (в tempdb) до тех пор, пока жив объект МТ или пока вы явно не удалите их запросом с инструкцией УНИЧТОЖИТЬ.
Часто одна из «выборок» на самом деле является таблицей значений, которую вы рассчитали в коде или получили из внешнего файла. Чтобы соединить её с данными из запроса, её нужно передать в МенеджерВременныхТаблиц в качестве параметра.
Проанализируем пример, где мы передаем таблицу ТЗ_ВнешниеДанные в запрос. Критически важно, чтобы колонки этой таблицы имели четко заданные типы, иначе запрос выдаст ошибку.
// Подготовка таблицы значений с типами
ТЗ = Новый ТаблицаЗначений;
КЧ = Новый КвалификаторыЧисла(15, 2);
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", КЧ);
ОписаниеТипаНоменклатура = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");
ТЗ.Колонки.Добавить("Номенклатура", ОписаниеТипаНоменклатура);
ТЗ.Колонки.Добавить("Количество", ОписаниеТипаЧисло);
// Заполнение данными...
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("001");
НоваяСтрока.Количество = 10;
// Использование в запросе
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| ВнешниеДанные.Номенклатура,
| ВнешниеДанные.Количество
|ПОМЕСТИТЬ ВТ_Внешние
|ИЗ
| &ВнешняяТЗ КАК ВнешниеДанные
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Внешние.Номенклатура,
| ВТ_Внешние.Количество,
| ЕСТЬNULL(Остатки.ВНаличииОстаток, 0) КАК СкладскойОстаток
|ИЗ
| ВТ_Внешние КАК ВТ_Внешние
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
| ПО ВТ_Внешние.Номенклатура = Остатки.Номенклатура";
Запрос.УстановитьПараметр("ВнешняяТЗ", ТЗ);
Результат = Запрос.Выполнить();
Рассмотрим несколько важных моментов, которые помогут избежать проблем при работе с временными таблицами:
ИНДЕКСИРОВАТЬ ПО сразу после помещения данных в ВТ.ТаблицаЗначений в запрос, убедитесь, что у каждой колонки есть ОписаниеТипов. Запрос не может создать структуру таблицы в SQL, если не знает точную длину строк или точность чисел.Запрос, как показано в первом методе, лучше использовать один пакетный запрос (тексты разделяются символом «;»). Это сокращает количество обращений к серверу БД и делает код чище.МТ.Закрыть() или уничтожать таблицы внутри запроса.Таким образом, соединение двух «выборок» в 1С — это всегда процесс перевода данных во временные таблицы и выполнение SQL-операции соединения. Использование МенеджерВременныхТаблиц является наиболее гибким и мощным инструментом для решения подобных архитектурных задач.