Как объединить две таблицы значений в 1С и избежать пустых колонок

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В процессе разработки часто возникает задача объединения данных из разных источников. Например, нам нужно собрать в один отчет данные об оборотах продаж из регистра накопления и данные о возвратах товаров напрямую из документов. Основная сложность здесь заключается не только в том, чтобы «склеить» две таблицы, но и в том, чтобы корректно сохранить типы данных, особенно для составных полей, таких как Регистратор.

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

Причина возникновения пустых значений при объединении

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

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

Способ 1. Объединение на уровне запроса (ОБЪЕДИНИТЬ ВСЕ)

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

Рассмотрим пример реализации такого запроса:


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

Запрос.Текст = ТекстЗапроса;
// Установка параметров
Запрос.УстановитьПараметр("Фирма", Фирма);
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);

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

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

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

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

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

  1. Создаем новый экземпляр МенеджерВременныхТаблиц.
  2. В первом запросе помещаем результат во временную таблицу (ПОМЕСТИТЬ ВТ_Продажи).
  3. Во втором запросе (используя тот же менеджер) помещаем результат во вторую таблицу (ПОМЕСТИТЬ ВТ_Возвраты).
  4. Выполняем финальный запрос, который выбирает данные из обеих временных таблиц через ОБЪЕДИНИТЬ ВСЕ.

Пример кода для работы с МВТ:


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

// Первый запрос
Запрос.Текст = "ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ1 ИЗ ...";
Запрос.Выполнить();

// Второй запрос
Запрос.Текст = "ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ2 ИЗ ...";
Запрос.Выполнить();

// Финальное объединение
Запрос.Текст = 
"ВЫБРАТЬ * ИЗ ВТ1
|ОБЪЕДИНИТЬ ВСЕ
|ВЫБРАТЬ * ИЗ ВТ2";
ТЗ = Запрос.Выполнить().Выгрузить();

Способ 3. Программное объединение с контролем типов

Если нам всё же необходимо объединить две таблицы значений программно (в коде), важно правильно инициализировать результирующую таблицу. Основной секрет — создать колонку Регистратор с составным типом данных.

Рассмотрим, как это сделать правильно:


// Определяем массив типов для составной колонки
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("ДокументСсылка.РеализацияТоваровУслуг"));
МассивТипов.Добавить(Тип("ДокументСсылка.ВозвратТоваровПоставщику"));
ОписаниеТипаРегистратор = Новый ОписаниеТипов(МассивТипов);

// Создаем итоговую ТЗ
ТЗ_Итог = Новый ТаблицаЗначений;
ТЗ_Итог.Колонки.Добавить("Регистратор", ОписаниеТипаРегистратор);
ТЗ_Итог.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

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

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

Важный момент: использование ЗаполнитьЗначенияСвойств значительно ускоряет процесс копирования строк по сравнению с ручным присваиванием каждого поля. Однако этот метод работает корректно только тогда, когда имена колонок в таблице-источнике и таблице-приемнике полностью совпадают.

Рекомендации по оптимизации

Проанализируем еще несколько советов, которые помогут сделать работу с таблицами более надежной:

Таким образом, мы выяснили, что наиболее правильным подходом является объединение данных на уровне СУБД с помощью 1С-запросов. Это гарантирует отсутствие проблем с типами данных и обеспечивает максимальную производительность системы.

← На главную