В практике разработчика 1С часто возникает задача объединения двух наборов данных «горизонтально». Представьте ситуацию: у вас есть первая таблица с двумя записями и вторая таблица с тремя записями. Между ними нет логической связи (ключей), но вам необходимо получить единый результат, где данные из первой таблицы стоят в первой колонке, а данные из второй — во второй, при этом недостающие значения должны быть заполнены NULL. В этой статье мы подробно разберем, как реализовать такое объединение средствами языка запросов и программными методами 1С.
Основная сложность в языке запросов 1С заключается в отсутствии стандартной функции ROW_NUMBER(), которая есть в классическом SQL. Поэтому, чтобы соединить две таблицы «по строкам», нам нужно сначала эти строки пронумеровать. Рассмотрим, как это сделать с помощью временных таблиц и левого соединения.
Проанализируем алгоритм решения этой задачи:
>=. Это позволит нам подсчитать количество записей, которые «меньше или равны» текущей, что и станет нашим порядковым номером.ПОЛНОЕ СОЕДИНЕНИЕ двух пронумерованных таблиц по полю номера.Рассмотрим пример кода запроса:
// Подготовка первой таблицы
ВЫБРАТЬ "А1" КАК Поле1 ПОМЕСТИТЬ ВТ_1 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "А2";
////////////////////////////////////////////////////////////////
// Подготовка второй таблицы
ВЫБРАТЬ "Б1" КАК Поле2 ПОМЕСТИТЬ ВТ_2 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Б2" ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "Б3";
////////////////////////////////////////////////////////////////
// Нумерация первой таблицы
ВЫБРАТЬ
КОЛИЧЕСТВО(ВТ_1_Доп.Поле1) КАК Номер,
ВТ_1.Поле1 КАК Поле1
ПОМЕСТИТЬ ВТ_1_Номера
ИЗ
ВТ_1 КАК ВТ_1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_1 КАК ВТ_1_Доп
ПО ВТ_1.Поле1 >= ВТ_1_Доп.Поле1
СГРУППИРОВАТЬ ПО
ВТ_1.Поле1;
////////////////////////////////////////////////////////////////
// Нумерация второй таблицы
ВЫБРАТЬ
КОЛИЧЕСТВО(ВТ_2_Доп.Поле2) КАК Номер,
ВТ_2.Поле2 КАК Поле2
ПОМЕСТИТЬ ВТ_2_Номера
ИЗ
ВТ_2 КАК ВТ_2
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_2 КАК ВТ_2_Доп
ПО ВТ_2.Поле2 >= ВТ_2_Доп.Поле2
СГРУППИРОВАТЬ ПО
ВТ_2.Поле2;
////////////////////////////////////////////////////////////////
// Финальное соединение
ВЫБРАТЬ
ВТ_1_Номера.Поле1 КАК Поле1,
ВТ_2_Номера.Поле2 КАК Поле2
ИЗ
ВТ_1_Номера КАК ВТ_1_Номера
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_2_Номера КАК ВТ_2_Номера
ПО ВТ_1_Номера.Номер = ВТ_2_Номера.Номер;
Важное замечание: Этот метод имеет квадратичную сложность O(n²). Если в ваших таблицах будет более тысячи строк, запрос может начать существенно «тормозить», так как для каждой строки выполняется поиск по всем остальным строкам. Также обратите внимание на условие ВТ_1.Поле1 >= ВТ_1_Доп.Поле1. Чтобы нумерация была корректной, поле, по которому идет сравнение, должно быть уникальным. Если значения могут дублироваться, используйте уникальные идентификаторы, такие как Ссылка.
Если данные уже получены в коде 1С, гораздо эффективнее использовать методы объекта ТаблицаЗначений. Проанализируем способ использования метода ЗагрузитьКолонку. Этот метод позволяет вставить массив данных непосредственно в существующую колонку таблицы по индексам строк.
Разберем по шагам, как это реализовать:
ЗагрузитьКолонку не расширяет таблицу-приемник автоматически.Посмотрим на пример реализации:
// Создаем исходные таблицы
ТЗ1 = Новый ТаблицаЗначений;
ТЗ1.Колонки.Добавить("S1");
ТЗ1.Добавить().S1 = "s11";
ТЗ1.Добавить().S1 = "s12";
ТЗ2 = Новый ТаблицаЗначений;
ТЗ2.Колонки.Добавить("S2");
ТЗ2.Добавить().S2 = "s21";
ТЗ2.Добавить().S2 = "s22";
ТЗ2.Добавить().S2 = "s23";
// Выясним, какая таблица длиннее
ТаблицаРезультат = ?(ТЗ1.Количество() > ТЗ2.Количество(), ТЗ1, ТЗ2);
ТаблицаИсточник = ?(ТЗ1.Количество() > ТЗ2.Количество(), ТЗ2, ТЗ1);
// Добавляем колонку из источника в результат
ИмяКолонкиИсточника = ТаблицаИсточник.Колонки[0].Имя;
ТаблицаРезультат.Колонки.Добавить(ИмяКолонкиИсточника);
// Переносим данные через массив
МассивДанных = ТаблицаИсточник.ВыгрузитьКолонку(ИмяКолонкиИсточника);
ТаблицаРезультат.ЗагрузитьКолонку(МассивДанных, ИмяКолонкиИсточника);
Этот способ работает мгновенно даже на десятках тысяч строк, так как манипуляции происходят в памяти на уровне оптимизированных внутренних механизмов платформы 1С:Предприятие.
Иногда начинающие разработчики пытаются использовать ПОЛНОЕ СОЕДИНЕНИЕ по условию ПО (ИСТИНА). Выясним причину, почему это плохая идея для данной задачи. Такое соединение называется Cross Join (декартово произведение). В результате вы получите не 3 строки (как в нашем примере), а 2 * 3 = 6 строк. Каждая запись первой таблицы соединится с каждой записью второй таблицы. Это полностью противоречит условию задачи, где требуется просто вывести данные параллельно друг другу.
Если вам необходимо объединить данные программно (как в Методе 2), но затем снова использовать их в сложном запросе, мы рекомендуем использовать МенеджерВременныхТаблиц. Вы можете подготовить итоговую таблицу в коде, а затем передать ее обратно в запрос следующим образом:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Помещаем подготовленную таблицу значений во временную таблицу запроса
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ИтоговаяТаблица ИЗ &ВнешняяТЗ КАК ВнешняяТЗ";
Запрос.УстановитьПараметр("ВнешняяТЗ", ТаблицаРезультат);
Запрос.Выполнить();
Это позволяет комбинировать гибкость программной обработки данных с мощностью механизмов запросов 1С.
Выбор метода зависит от объема данных и места выполнения кода:
ЗагрузитьКолонку(). Это самый производительный и надежный вариант.Мы рассмотрели основные способы «горизонтального» объединения таблиц. Помните, что платформа 1С предоставляет множество инструментов для манипуляции данными, и правильный выбор инструмента напрямую влияет на скорость работы вашей системы.