В практике разработчика 1С часто возникает задача соединения двух выборок (или таблиц), где ключевое поле не является уникальным. При обычном использовании конструкции ЛЕВОЕ СОЕДИНЕНИЕ по общему полю возникает эффект «размножения» строк: если в первой таблице три строки с ключом «А», и во второй таблице две строки с таким же ключом, результатом запроса станет шесть строк (3 * 2). Однако часто требуется сопоставить строки «один к одному», то есть первую строку из первой таблицы — с первой строкой из второй, вторую — со второй и так далее.
Поскольку в языке запросов 1С отсутствуют стандартные SQL-функции ранжирования (например, ROW_NUMBER() OVER(PARTITION BY...)), нам приходится использовать обходные пути для быстрого анализа данных. Давайте разберем наиболее эффективные методы решения этой задачи.
Рассмотрим современный способ, который стал доступен с выходом новых версий платформы. Основная идея заключается в том, чтобы пронумеровать записи в обеих таблицах и использовать этот номер как дополнительное поле связи, предварительно проведя анализ структуры метаданных. Проанализируем, как это реализовать в коде запроса.
Сначала подготовим первую временную таблицу. Важным нюансом здесь является использование конструкции ПЕРВЫЕ совместно с УПОРЯДОЧИТЬ ПО. Без явной сортировки порядок записей в СУБД (особенно в PostgreSQL) не гарантирован, что приведет к непредсказуемым результатам нумерации.
ВЫБРАТЬ ПЕРВЫЕ 1000000
Т1.НомерСтроки КАК НомерСтроки,
Т1.Текст КАК Текст,
АВТОНОМЕРЗАПИСИ() КАК НомерДляСвязи
ПОМЕСТИТЬ ВТ_Таблица1
ИЗ
&Таблица1 КАК Т1
УПОРЯДОЧИТЬ ПО
Т1.НомерСтроки;
Затем аналогичным образом подготовим вторую временную таблицу. Разберем ситуацию: чтобы соединение произошло корректно, НомерДляСвязи в обеих таблицах должен иметь общую логику формирования. Если мы просто нумеруем все записи подряд, то это сработает только в том случае, если общее количество строк в таблицах совпадает и они сгруппированы по ключам.
ВЫБРАТЬ ПЕРВЫЕ 1000000
Т2.НомерСтроки КАК НомерСтроки,
Т2.Текст2 КАК Текст2,
АВТОНОМЕРЗАПИСИ() КАК НомерДляСвязи
ПОМЕСТИТЬ ВТ_Таблица2
ИЗ
&Таблица2 КАК Т2
УПОРЯДОЧИТЬ ПО
Т2.НомерСтроки;
После подготовки данных, используя консоль запросов УФ, мы можем выполнить финальное соединение по двум полям: НомерСтроки (основной ключ) и НомерДляСвязи (порядковый номер). Это позволит нам сопоставить записи без дублей.
Проанализируем технический нюанс, упомянутый в обсуждении: иногда АВТОНОМЕРЗАПИСИ() может начинаться не с единицы или иметь разные стартовые значения в разных временных таблицах. Чтобы обеспечить стопроцентную вероятность успеха, мы можем вычислить минимальный номер для каждой группы и вычесть его из текущего номера.
Рассмотрим алгоритм действий:
НомерСтроки) вычисляем МИНИМУМ(НомерДляСвязи).Т1.НомерДляСвязи - Мин1.МинимальныйНомер = Т2.НомерДляСвязи - Мин2.МинимальныйНомер.Если вы работаете на версии платформы ниже 8.3.20, где АВТОНОМЕРЗАПИСИ недоступен, поможет управляемая консоль запросов, позволяющая использовать классический метод имитации порядкового номера внутри группы. Посмотрим на этот алгоритм подробнее.
Мы соединяем таблицу саму с собой по условию: совпадение ключа и наличие уникального дополнительного поля (например, ссылки или другого уникального идентификатора), которое меньше или равно значению в текущей строке.
ВЫБРАТЬ
Т1.Ключ,
Т1.Данные,
КОЛИЧЕСТВО(Т2.Ключ) КАК ПорядковыйНомер
ИЗ
ИсходнаяТаблица КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ ИсходнаяТаблица КАК Т2
ПО Т1.Ключ = Т2.Ключ
И Т1.УникальноеПоле >= Т2.УникальноеПоле
СГРУППИРОВАТЬ ПО
Т1.Ключ,
Т1.Данные
Важно: этот метод крайне ресурсозатратный на больших объемах данных (сложность O(n^2)), поэтому его следует применять с осторожностью и только для небольших выборок.
Если логика запроса становится слишком сложной или непроизводительной, стоит рассмотреть вариант постобработки данных в коде 1С. Разберем пример, предложенный участниками обсуждения, где используется метод Найти() и удаление строк для предотвращения повторного использования данных.
// Таблица1 и Таблица2 уже заполнены данными
Для Каждого Стр1 Из Таблица1 Цикл
НоваяСтрока = ТаблицаИтог.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Стр1);
// Ищем соответствующую строку во второй таблице по ключу
ПараметрыОтбора = Новый Структура("НомерСтроки", Стр1.НомерСтроки);
СтрокаТ2 = Таблица2.Найти(Стр1.НомерСтроки, "НомерСтроки");
Если СтрокаТ2 <> Неопределено Тогда
НоваяСтрока.Текст2 = СтрокаТ2.Текст2;
// Удаляем найденную строку, чтобы она не подтянулась повторно
Таблица2.Удалить(СтрокаТ2);
КонецЕсли;
КонецЦикла;
Проанализируем ситуацию: данный метод идеально подходит для небольших и средних таблиц значений. Он прост в отладке и гарантирует, что каждая запись из Таблица2 будет использована только один раз. Однако стоит помнить, что удаление строки из середины большой таблицы — операция не самая быстрая.
Подводя итог, можно выделить следующие правила выбора решения:
АВТОНОМЕРЗАПИСИ и временных таблиц.ПОЛНОЕ СОЕДИНЕНИЕ после нумерации строк.Выясним причину частых ошибок: чаще всего проблемы возникают из-за отсутствия сортировки при нумерации. Всегда проверяйте, что в запросах с АВТОНОМЕРЗАПИСИ или ПЕРВЫЕ присутствует УПОРЯДОЧИТЬ ПО, иначе при каждом запуске результат может быть разным.