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

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

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

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

Предварительная подготовка данных: удаление дубликатов

Прежде чем приступать к комбинированию строк, важно убедиться, что исходные списки содержат только уникальные элементы. Если в исходных таблицах присутствуют повторяющиеся значения, итоговая выборка будет раздута одинаковыми комбинациями. Для этого выполним предварительное сворачивание каждой исходной таблицы значений перед передачей в обработку:


Таблица1.Свернуть("Значение");
Таблица2.Свернуть("Значение");
Таблица3.Свернуть("Значение");

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

Способ 1. Использование языка запросов (перекрестное соединение CROSS JOIN)

Если количество таблиц фиксировано и невелико (например, 2–4 таблицы), наиболее компактным и быстрым способом в 1С является формирование запроса с соединением таблиц без условий связи (декартово произведение). В языке запросов 1С перечисление источников через запятую в секции ИЗ выполняет именно перекрестное соединение.

Посмотрим на пример формирования такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	Т1.Значение КАК Поле1,
    |	Т2.Значение КАК Поле2,
    |	Т3.Значение КАК Поле3
    |ИЗ
    |	&ТЗ1 КАК Т1,
    |	&ТЗ2 КАК Т2,
    |	&ТЗ3 КАК Т3";

Запрос.УстановитьПараметр("ТЗ1", Таблица1);
Запрос.УстановитьПараметр("ТЗ2", Таблица2);
Запрос.УстановитьПараметр("ТЗ3", Таблица3);

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

Ключевое слово РАЗЛИЧНЫЕ исключает дублирующиеся строки на уровне СУБД, даже если предварительное сворачивание таблиц не выполнялось.

Способ 2. Динамическое построение запроса для произвольного количества таблиц

Когда число входящих наборов данных заранее неизвестно и они передаются, например, в виде массива таблиц значений МассивТаблиц, текст запроса можно сформировать программно в цикле:


Запрос = Новый Запрос;

ТекстПоля = "";
ТекстИсточники = "";

Для Номер = 0 По МассивТаблиц.ВГраница() Цикл
    ИмяПараметра = "ТЗ" + Формат(Номер, "ЧГ=0");
    ИмяАлиаса = "Т" + Формат(Номер, "ЧГ=0");
    
    Разделитель = ?(Номер = 0, "", ", ");
    ТекстПоля = ТекстПоля + Разделитель + ИмяАлиаса + ".Значение КАК Поле" + Формат(Номер, "ЧГ=0");
    ТекстИсточники = ТекстИсточники + Разделитель + "&" + ИмяПараметра + " КАК " + ИмяАлиаса;
    
    Запрос.УстановитьПараметр(ИмяПараметра, МассивТаблиц[Номер]);
КонецЦикла;

Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ " + ТекстПоля + "
    |ИЗ " + ТекстИсточники;

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

Способ 3. Универсальный итеративный алгоритм без рекурсии (принцип одометра)

У выполнения декартова произведения через запросы есть существенное ограничение: запрос пытается вычислить и материализовать всю выборку целиком. Если таблиц много или в них по тысяче строк (1000 × 1000 × 1000 = 1 000 000 000 комбинаций), база данных исчерпает свободное место в системной базе tempdb или платформа завершит процесс с ошибкой нехватки оперативной памяти.

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


// МассивКоллекций - массив массивов или таблиц с элементами для перебора
КоличествоКоллекций = МассивКоллекций.Количество();

// Проверим, что нет пустых коллекций
Для Каждого Коллекция Из МассивКоллекций Цикл
    Если Коллекция.Количество() = 0 Тогда
        Возврат; // При пустом множестве декартово произведение пусто
    КонецЕсли;
КонецЦикла;

// Инициализируем текущие индексы нулями
Индексы = Новый Массив(КоличествоКоллекций);
Для Индекс = 0 По КоличествоКоллекций - 1 Цикл
    Индексы[Индекс] = 0;
КонецЦикла;

Пока Истина Цикл
    // 1. Получаем текущую комбинацию значений
    ТекущаяКомбинация = Новый Массив;
    Для Индекс = 0 По КоличествоКоллекций - 1 Цикл
        ЗначениеЭлемента = МассивКоллекций[Индекс][Индексы[Индекс]];
        ТекущаяКомбинация.Добавить(ЗначениеЭлемента);
    КонецЦикла;
    
    // 2. Обрабатываем полученную комбинацию (запись в файл, расчет, фильтрация)
    // ОбработатьКомбинацию(ТекущаяКомбинация);
    
    // 3. Сдвигаем счетчик индексов справа налево
    Позиция = КоличествоКоллекций - 1;
    Пока Позиция >= 0 Цикл
        Индексы[Позиция] = Индексы[Позиция] + 1;
        Если Индексы[Позиция] < МассивКоллекций[Позиция].Количество() Тогда
            // Переноса разряда нет, переходим к следующей комбинации
            Прервать;
        Иначе
            // Произошло переполнение в текущем разряде - сбрасываем в ноль и идем влево
            Индексы[Позиция] = 0;
            Позиция = Позиция - 1;
        КонецЕсли;
    КонецЦикла;
    
    // Если переполнение дошло до крайней левой позиции, весь перебор закончен
    Если Позиция < 0 Тогда
        Прервать;
    КонецЕсли;
КонецЦикла;

Преимущества такого подхода:

  1. Минимальный расход памяти: служебный массив индексов занимает фиксированный объем памяти O(N), где N — количество коллекций, независимо от миллиардов возможных исходов.
  2. Потоковая обработка: мы можем сразу анализировать вариант, проверять условия отбора и отбрасывать неподходящие строки без сохранения в массив или ТаблицаЗначений.
  3. Отсутствие риска переполнения стека: алгоритм не использует рекурсию, что исключает ошибки переполнения стека вызовов и ускоряет работу.

Рекомендации по оптимизации для больших объемов данных

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

  1. Отказ от материализации всего результата: никогда не выгружайте огромный результат запроса в единый объект ТаблицаЗначений. Если необходимо выгрузить данные во внешнюю систему, используйте потоковую запись через объекты ЗаписьТекста, ЗаписьJSON или ЗаписьXML.
  2. Параллелизация вычислений: генерация комбинаций легко делится на независимые потоки. Разделите элементы первой коллекции по нескольким фоновым заданиям с помощью метода ФоновыеЗадания.Выполнить, передав в каждый поток свою порцию данных.
  3. Ранняя фильтрация: если исходные данные хранятся в регистрах или справочниках базы, накладывайте условия ГДЕ до объединения таблиц. Это позволит СУБД отсечь ненужные ветки перебора на самом раннем этапе.
← На главную