В разработке на платформе 1С часто возникает задача: есть две таблицы значений (ТЗ), сформированные в памяти, и их нужно соединить по одному или нескольким ключевым полям, аналогнично операции JOIN в SQL. Например, в одной таблице остатки товаров, в другой — их цены, и нужно получить итоговую таблицу с остатками и ценами для каждого товара.
Возникает вопрос: как это сделать наиболее эффективно? Использовать язык запросов 1С, передав таблицы в качестве параметров, или написать алгоритм соединения средствами самого языка 1С? Разберем оба подхода, их плюсы, минусы и производительность.
Этот метод заключается в том, чтобы передать обе таблицы значений в объект Запрос как параметры и выполнить их соединение на стороне СУБД с помощью стандартного синтаксиса языка запросов. Такой код выглядит лаконично и понятно для любого разработчика, знакомого с SQL.
Посмотрим на пример. Допустим, у нас есть две таблицы. Первая содержит информацию о товарах и их количестве, а вторая — о тех же товарах и ценах. Нам нужно получить итоговую таблицу с товаром, количеством и ценой.
// Создаем и заполняем первую таблицу (источник данных А)
ТаблицаКоличества = Новый ТаблицаЗначений;
ТаблицаКоличества.Колонки.Добавить("Товар", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаКоличества.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
// ... (код заполнения таблицы) ...
// Создаем и заполняем вторую таблицу (источник данных Б)
ТаблицаЦен = Новый ТаблицаЗначений;
ТаблицаЦен.Колонки.Добавить("Товар", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаЦен.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
// ... (код заполнения таблицы) ...
// Соединяем таблицы с помощью запроса
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТаблицаКоличества", ТаблицаКоличества);
Запрос.УстановитьПараметр("ТаблицаЦен", ТаблицаЦен);
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаКоличества.Товар,
| ТаблицаКоличества.Количество,
| ЕСТЬNULL(ТаблицаЦен.Цена, 0) КАК Цена
|ИЗ
| &ТаблицаКоличества КАК ТаблицаКоличества
| ЛЕВОЕ СОЕДИНЕНИЕ &ТаблицаЦен КАК ТаблицаЦен
| ПО ТаблицаКоличества.Товар = ТаблицаЦен.Товар";
Результат = Запрос.Выполнить().Выгрузить();
Давайте проанализируем сильные и слабые стороны этого решения.
Преимущества:
bulk insert), где создаются временные таблицы. После этого сам процесс соединения выполняется мощным и оптимизированным ядром СУБД (например, MS SQL или PostgreSQL), которое написано на низкоуровневых языках и работает гораздо быстрее интерпретатора 1С. Тесты показывают, что на десятках тысяч строк запрос может быть в 5-10 раз быстрее, чем код на 1С.ЛЕВОЕ СОЕДИНЕНИЕ ... ПО ... является общепринятым стандартом и понятна с первого взгляда. Такой код легко читать, модифицировать и поддерживать в будущем.JOIN в СУБД отлажены годами и работают предсказуемо. При написании ручного алгоритма на 1С легко допустить логическую ошибку, особенно в обработке дублирующихся ключей или крайних случаев.Недостатки и особенности:
tempdb). В системах с чрезвычайно высокой нагрузкой на СУБД это может быть фактором, который стоит учитывать.Альтернативный подход — не привлекать СУБД, а реализовать логику соединения непосредственно в коде 1С. Существует несколько способов это сделать, от крайне неэффективных до вполне рабочих.
Это классический и наиболее производительный алгоритм для соединения таблиц в коде. Суть его в следующем:
Пример реализации для левого соединения двух таблиц по числовому ключу:
// Предполагаем, что Таблица1 и Таблица2 уже существуют и заполнены
// Таблица1 - основная, в нее будем дописывать данные
// 1. Сортируем обе таблицы по ключу
Таблица1.Сортировать("КолонкаСвязи");
Таблица2.Сортировать("КолонкаСвязи");
// 2. Добавляем в первую таблицу колонку для данных из второй
Таблица1.Колонки.Добавить("ДанныеИзТаблицы2");
// 3. Алгоритм слияния
ИндексТаблицы1 = 0;
ИндексТаблицы2 = 0;
Пока ИндексТаблицы1 < Таблица1.Количество() И ИндексТаблицы2 < Таблица2.Количество() Цикл
СтрокаТаблицы1 = Таблица1[ИндексТаблицы1];
СтрокаТаблицы2 = Таблица2[ИндексТаблицы2];
Если СтрокаТаблицы1.КолонкаСвязи > СтрокаТаблицы2.КолонкаСвязи Тогда
// Ключ во второй таблице меньше, двигаемся по ней дальше
ИндексТаблицы2 = ИндексТаблицы2 + 1;
ИначеЕсли СтрокаТаблицы1.КолонкаСвязи < СтрокаТаблицы2.КолонкаСвязи Тогда
// Ключ в первой таблице меньше, для него нет соответствия, двигаемся по ней
ИндексТаблицы1 = ИндексТаблицы1 + 1;
Иначе // Ключи равны
// Нашли соответствие, заполняем данные и двигаемся по обеим таблицам
СтрокаТаблицы1.ДанныеИзТаблицы2 = СтрокаТаблицы2.ДанныеИзТаблицы2;
ИндексТаблицы1 = ИндексТаблицы1 + 1;
ИндексТаблицы2 = ИндексТаблицы2 + 1;
КонецЕсли;
КонецЦикла;
Преимущества:
Недостатки:
СоответствиеДля простых сценариев левого соединения, когда из второй таблицы нужно "подтянуть" данные в первую, очень эффективным может быть использование объекта Соответствие.
Соответствие, где ключ — это поле соединения.Соответствия по ключу.
// Создаем и заполняем соответствие из второй, меньшей таблицы
КартаДанных = Новый Соответствие;
Для Каждого СтрокаТ2 Из Таблица2 Цикл
КартаДанных.Вставить(СтрокаТ2.Ключ, СтрокаТ2.Значение);
КонецЦикла;
// Добавляем колонку в первую таблицу
Таблица1.Колонки.Добавить("ПодтянутоеЗначение");
// Обходим основную таблицу и заполняем данные
Для Каждого СтрокаТ1 Из Таблица1 Цикл
СтрокаТ1.ПодтянутоеЗначение = КартаДанных[СтрокаТ1.Ключ];
КонецЦикла;
Этот метод часто оказывается быстрее, чем Merge Join, и он намного проще в реализации и чтении. Однако он хорошо подходит только для соединений "один к одному" или "многие к одному".
Важно: Никогда не используйте следующий подход для больших таблиц. Он заключается в том, чтобы в цикле по одной таблице искать соответствующую строку в другой с помощью метода НайтиСтроки() или Найти().
// ТАК ДЕЛАТЬ НЕЛЬЗЯ НА БОЛЬШИХ ОБЪЕМАХ!
Для Каждого СтрокаТ1 Из Таблица1 Цикл
НайденныеСтроки = Таблица2.НайтиСтроки(Новый Структура("Ключ", СтрокаТ1.Ключ));
Если НайденныеСтроки.Количество() > 0 Тогда
// ... делаем что-то ...
КонецЕсли;
КонецЦикла;
Сложность такого алгоритма близка к квадратичной (O(N*M)), и его производительность катастрофически падает с ростом числа строк в таблицах.
На основе анализа и многочисленных тестов производительности, проведенных сообществом, можно сделать следующие выводы:
В подавляющем большинстве случаев для соединения таблиц значений следует использовать запрос к СУБД.
Это самый быстрый, надежный и читаемый способ. Современная платформа 1С отлично оптимизирована для таких операций. Код, представленный в самом начале, не является "говнокодом", а наоборот — примером эффективной и правильной практики.
Использование кода на 1С (Merge Join или Соответствие) может быть оправдано в редких, специфических случаях:
Таким образом, не бойтесь "дергать СУБД" для обработки данных, которые уже находятся в памяти. Часто это самый прямой и эффективный путь к решению задачи.