Что быстрее и правильнее: соединять таблицы значений запросом или кодом 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В разработке на платформе 1С часто возникает задача: есть две таблицы значений (ТЗ), сформированные в памяти, и их нужно соединить по одному или нескольким ключевым полям, аналогнично операции JOIN в SQL. Например, в одной таблице остатки товаров, в другой — их цены, и нужно получить итоговую таблицу с остатками и ценами для каждого товара.

Возникает вопрос: как это сделать наиболее эффективно? Использовать язык запросов 1С, передав таблицы в качестве параметров, или написать алгоритм соединения средствами самого языка 1С? Разберем оба подхода, их плюсы, минусы и производительность.

Решение 1: Соединение с помощью Запроса к СУБД (рекомендуемый подход)

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

Посмотрим на пример. Допустим, у нас есть две таблицы. Первая содержит информацию о товарах и их количестве, а вторая — о тех же товарах и ценах. Нам нужно получить итоговую таблицу с товаром, количеством и ценой.


// Создаем и заполняем первую таблицу (источник данных А)
ТаблицаКоличества = Новый ТаблицаЗначений;
ТаблицаКоличества.Колонки.Добавить("Товар", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаКоличества.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
// ... (код заполнения таблицы) ...

// Создаем и заполняем вторую таблицу (источник данных Б)
ТаблицаЦен = Новый ТаблицаЗначений;
ТаблицаЦен.Колонки.Добавить("Товар", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаЦен.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
// ... (код заполнения таблицы) ...

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

Запрос.Текст = 
"ВЫБРАТЬ
|	ТаблицаКоличества.Товар,
|	ТаблицаКоличества.Количество,
|	ЕСТЬNULL(ТаблицаЦен.Цена, 0) КАК Цена
|ИЗ
|	&ТаблицаКоличества КАК ТаблицаКоличества
|		ЛЕВОЕ СОЕДИНЕНИЕ &ТаблицаЦен КАК ТаблицаЦен
|		ПО ТаблицаКоличества.Товар = ТаблицаЦен.Товар";

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

Давайте проанализируем сильные и слабые стороны этого решения.

Преимущества:

  1. Высокая производительность. Вопреки устаревшим мнениям, на современных версиях платформы 1С этот способ очень быстр. Платформа эффективно передает данные из таблиц значений на сервер СУБД (используя механизмы, схожие с bulk insert), где создаются временные таблицы. После этого сам процесс соединения выполняется мощным и оптимизированным ядром СУБД (например, MS SQL или PostgreSQL), которое написано на низкоуровневых языках и работает гораздо быстрее интерпретатора 1С. Тесты показывают, что на десятках тысяч строк запрос может быть в 5-10 раз быстрее, чем код на 1С.
  2. Читаемость и поддержка. Код с запросом декларативен. Вы описываете, что хотите получить, а не как. Конструкция ЛЕВОЕ СОЕДИНЕНИЕ ... ПО ... является общепринятым стандартом и понятна с первого взгляда. Такой код легко читать, модифицировать и поддерживать в будущем.
  3. Надежность. Стандартные операции JOIN в СУБД отлажены годами и работают предсказуемо. При написании ручного алгоритма на 1С легко допустить логическую ошибку, особенно в обработке дублирующихся ключей или крайних случаев.

Недостатки и особенности:

  1. Нагрузка на СУБД. Операция выполняется на сервере базы данных, потребляя его ресурсы (процессор, оперативную память для временных таблиц в tempdb). В системах с чрезвычайно высокой нагрузкой на СУБД это может быть фактором, который стоит учитывать.
  2. Передача данных по сети. Таблицы передаются от сервера 1С к серверу СУБД. На гигабитной локальной сети это влияние минимально, но его стоит иметь в виду.

Решение 2: Соединение средствами языка 1С

Альтернативный подход — не привлекать СУБД, а реализовать логику соединения непосредственно в коде 1С. Существует несколько способов это сделать, от крайне неэффективных до вполне рабочих.

Подход 2.1: Сортировка и слияние (Merge Join)

Это классический и наиболее производительный алгоритм для соединения таблиц в коде. Суть его в следующем:

  1. Обе таблицы сортируются по ключевому полю (или полям) соединения.
  2. Запускается цикл, который одновременно "пробегает" по обеим отсортированным таблицам с помощью двух итераторов (индексов).
  3. На каждой итерации сравниваются ключи. В зависимости от результата сравнения, один из итераторов сдвигается, а при совпадении ключей данные из второй таблицы добавляются к строке первой.

Пример реализации для левого соединения двух таблиц по числовому ключу:


// Предполагаем, что Таблица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: Использование объекта Соответствие

Для простых сценариев левого соединения, когда из второй таблицы нужно "подтянуть" данные в первую, очень эффективным может быть использование объекта Соответствие.

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

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

// Добавляем колонку в первую таблицу
Таблица1.Колонки.Добавить("ПодтянутоеЗначение");

// Обходим основную таблицу и заполняем данные
Для Каждого СтрокаТ1 Из Таблица1 Цикл
	СтрокаТ1.ПодтянутоеЗначение = КартаДанных[СтрокаТ1.Ключ];
КонецЦикла;

Этот метод часто оказывается быстрее, чем Merge Join, и он намного проще в реализации и чтении. Однако он хорошо подходит только для соединений "один к одному" или "многие к одному".

Подход 2.3: Поиск в цикле (АНТИПАТТЕРН)

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


// ТАК ДЕЛАТЬ НЕЛЬЗЯ НА БОЛЬШИХ ОБЪЕМАХ!
Для Каждого СтрокаТ1 Из Таблица1 Цикл
	НайденныеСтроки = Таблица2.НайтиСтроки(Новый Структура("Ключ", СтрокаТ1.Ключ));
	Если НайденныеСтроки.Количество() > 0 Тогда
		// ... делаем что-то ...
	КонецЕсли;
КонецЦикла;

Сложность такого алгоритма близка к квадратичной (O(N*M)), и его производительность катастрофически падает с ростом числа строк в таблицах.

Выводы и рекомендации

На основе анализа и многочисленных тестов производительности, проведенных сообществом, можно сделать следующие выводы:

В подавляющем большинстве случаев для соединения таблиц значений следует использовать запрос к СУБД.

Это самый быстрый, надежный и читаемый способ. Современная платформа 1С отлично оптимизирована для таких операций. Код, представленный в самом начале, не является "говнокодом", а наоборот — примером эффективной и правильной практики.

Использование кода на 1С (Merge Join или Соответствие) может быть оправдано в редких, специфических случаях:

Таким образом, не бойтесь "дергать СУБД" для обработки данных, которые уже находятся в памяти. Часто это самый прямой и эффективный путь к решению задачи.

← На главную