Как существенно ускорить поиск и сопоставление таблиц значений в 1С

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

При разработке сложных отчетов, интеграционных решений и обработок обмена данными в среде 1С:Предприятие разработчики регулярно сталкиваются с необходимостью сопоставления больших объемов информации. Типичный сценарий: получение таблицы данных из внешней системы (например, по HTTP-протоколу в формате JSON или XML) и сравнение ее с локальной таблицей документов или справочников. Когда размер таблиц превышает десятки тысяч строк, стандартный линейный поиск начинает критически тормозить интерфейс и перегружать сервер.

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

Почему стандартный поиск по таблице значений начинает тормозить?

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

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

Решение №1: Правильное построение составных индексов в Таблице Значений

Первая и самая распространенная ошибка при работе с индексами в ТаблицеЗначений — это их неправильное объявление. Давайте посмотрим на пример некорректного кода, который часто можно встретить на практике:


ТЗСкдНд1.Индексы.Добавить("ДокументУИД");
ТЗСкдНд1.Индексы.Добавить("НоменклатураУИД");
ТЗСкдНд1.Индексы.Добавить("ПартияУИД");

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

Для ускорения многокритериального поиска необходимо создавать единый составной (композитный) индекс. Разберем правильный синтаксис:


// Создаем один составной индекс по трем колонкам через запятую
ТЗСкдНд1.Индексы.Добавить("ДокументУИД,НоменклатураУИД,ПартияУИД");

// Формируем структуру отбора, точно соответствующую полям индекса
Отбор = Новый Структура;
Отбор.Вставить("ДокументУИД", ТекущийДокументУИД);
Отбор.Вставить("НоменклатураУИД", ТекущаяНоменклатураУИД);
Отбор.Вставить("ПартияУИД", ТекущаяПартияУИД);

// Поиск по составному индексу отработает мгновенно
НайденныеСтроки = ТЗСкдНд1.НайтиСтроки(Отбор);

Важно помнить о скрытых нюансах работы индексов в ТаблицеЗначений:

  1. Ленивое построение: Индекс физически не создается в памяти в момент вызова метода Индексы.Добавить(). Платформа построит его только при первом фактическом вызове метода поиска (например, НайтиСтроки()).
  2. Сброс индекса при изменении ТЗ: Если внутри цикла поиска вы добавляете новые строки в индексированную таблицу, удаляете их или изменяете значения в индексированных колонках, платформа помечает индекс как неактуальный. При следующем поиске индекс будет полностью перестраиваться заново, что полностью нивелирует весь выигрыш в скорости. Правило: сначала полностью заполняем таблицу, затем индексируем и только после этого осуществляем поиск без изменения состава данных.
  3. Селективность полей: Первыми в строке описания составного индекса всегда указывайте колонки с наибольшей уникальностью значений (высокой селективностью). Это позволит платформе быстрее отсекать неподходящие ветви индекса при поиске.

Решение №2: Использование объекта «Соответствие» (Хеширование)

Если вам требуется сопоставить данные двух таблиц по уникальному ключу (даже если он состоит из нескольких полей), отличной и зачастую более быстрой альтернативой индексам является использование встроенного объекта Соответствие. Этот объект работает по принципу хеш-таблицы, обеспечивая скорость поиска элемента за константное время O(1), независимо от общего размера коллекции.

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

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

Посмотрим на пример реализации данного метода в коде:


// Шаг 1: Создаем соответствие для хеширования данных первой таблицы
КартаПоиска = Новый Соответствие;

Для Каждого СтрокаТЗ1 Из ТЗ_Источник1 Цикл
    // Собираем уникальный строковый ключ-хеш
    КлючХеш = Строка(СтрокаТЗ1.ДокументУИД) + "|" + 
              Строка(СтрокаТЗ1.НоменклатураУИД) + "|" + 
              Строка(СтрокаТЗ1.ПартияУИД);
              
    // Заполняем соответствие. Если ключи могут дублироваться, 
    // в качестве значения можно хранить Массив строк.
    КартаПоиска.Вставить(КлючХеш, СтрокаТЗ1);
КонецЦикла;

// Шаг 2: Быстрое сопоставление со второй таблицей
Для Каждого СтрокаТЗ2 Из ТЗ_Источник2 Цикл
    КлючПоиска = Строка(СтрокаТЗ2.ДокументУИД) + "|" + 
                 Строка(СтрокаТЗ2.НоменклатураУИД) + "|" + 
                 Строка(СтрокаТЗ2.ПартияУИД);
                 
    НайденнаяСтрока = КартаПоиска.Получить(КлючПоиска);
    Если НайденнаяСтрока <> Неопределено Тогда
        // Строка успешно сопоставлена, выполняем необходимую логику обработки
        СтрокаТЗ2.Цена = НайденнаяСтрока.Цена;
    КонецЕсли;
КонецЦикла;

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

Решение №3: Метод свертки и сортировки («Знак»-свертка)

Рассмотрим оригинальный и чрезвычайно быстрый алгоритм сопоставления двух таблиц на встроенном языке 1С без написания ресурсоемких циклов поиска. Этот метод основан на использовании базовых оптимизированных методов платформы: ЗаполнитьЗначения() и Свернуть().

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

  1. Создаем результирующую таблицу, которая содержит все ключевые поля сравнения и одну служебную числовую колонку с именем Знак.
  2. Выгружаем в результирующую таблицу строки из первой таблицы значений и для всех этих строк заполняем колонку Знак числом 1.
  3. Добавляем (копируем) в эту же результирующую таблицу все строки из второй таблицы значений, но для них заполняем колонку Знак числом -1.
  4. Вызываем метод РезультирующаяТЗ.Свернуть("ДокументУИД, НоменклатураУИД, ПартияУИД", "Знак").

Давайте проанализируем, что произойдет после свертки: строки, которые полностью совпали по всем ключевым полям в обеих таблицах, объединятся, а значения в колонке Знак сложатся (1 + (-1) = 0). Строки, у которых значение колонки Знак осталось равным 1, присутствуют только в первой таблице. Строки со значением -1 — только во второй.

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


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

// Загружаем данные первой таблицы (знак 1)
Для Каждого Стр Из ТЗ_Источник1 Цикл
    ЗаполнитьЗначенияСвойств(ТЗ_Сравнения.Добавить(), Стр);
КонецЦикла;
ТЗ_Сравнения.ЗаполнитьЗначения(1, "Знак");

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

// Заполняем знак -1 только для вновь добавленных строк
Для Индекс = ГраницаДобавления По ТЗ_Сравнения.Количество() - 1 Цикл
    ТЗ_Сравнения[Индекс].Знак = -1;
КонецЦикла;

// Сворачиваем таблицу по ключевым полям
ТЗ_Сравнения.Свернуть("ДокументУИД, НоменклатураУИД, ПартияУИД", "Знак");

// Теперь мы можем быстро отфильтровать расхождения
СтрокиТолькоВПервой = ТЗ_Сравнения.НайтиСтроки(Новый Структура("Знак", 1));
СтрокиТолькоВоВторой = ТЗ_Сравнения.НайтиСтроки(Новый Структура("Знак", -1));
СовпавшиеСтроки = ТЗ_Сравнения.НайтиСтроки(Новый Структура("Знак", 0));

Этот метод работает на уровне низкоуровневых оптимизированных алгоритмов платформы 1С и СУБД, превосходя по скорости любые циклы сопоставления на встроенном языке программирования.

Решение №4: Перенос обработки во временные таблицы Запроса

Когда вычисления на встроенном языке 1С становятся узким местом системы, наиболее логичным шагом является перенос обработки на сторону СУБД. Процессор запросов SQL оптимизирует соединение таблиц значительно лучше, чем это делает интерпретатор встроенного кода.

Вы можете передать имеющиеся таблицы значений в качестве параметров запроса, поместить их во временные таблицы, обязательно проиндексировать их средствами SQL по ключевым полям и связать через конструкции ПОЛНОЕ СОЕДИНЕНИЕ или ЛЕВОЕ СОЕДИНЕНИЕ.

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


// Создаем запрос с менеджером временных таблиц
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Передаем исходные таблицы значений в параметры запроса
Запрос.УстановитьПараметр("Таблица1", ТЗ_Источник1);
Запрос.УстановитьПараметр("Таблица2", ТЗ_Источник2);

// Помещаем первую таблицу во временную таблицу и ИНДЕКСИРУЕМ ее
Запрос.Текст = 
"ВЫБРАТЬ
|	Т1.ДокументУИД КАК ДокументУИД,
|	Т1.НоменклатураУИД КАК НоменклатураУИД,
|	Т1.ПартияУИД КАК ПартияУИД,
|	Т1.Количество КАК Количество1
|ПОМЕСТИТЬ ВТ_Таблица1
|ИЗ
|	&Таблица1 КАК Т1
|
|ИНДЕКСИРОВАТЬ ПО
|	ДокументУИД,
|	НоменклатураУИД,
|	ПартияУИД;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	Т2.ДокументУИД КАК ДокументУИД,
|	Т2.НоменклатураУИД КАК НоменклатураУИД,
|	Т2.ПартияУИД КАК ПартияУИД,
|	Т2.Количество КАК Количество2
|ПОМЕСТИТЬ ВТ_Таблица2
|ИЗ
|	&Таблица2 КАК Т2
|
|ИНДЕКСИРОВАТЬ ПО
|	ДокументУИД,
|	НоменклатураУИД,
|	ПартияУИД;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ЕСТЬNULL(ВТ1.ДокументУИД, ВТ2.ДокументУИД) КАК ДокументУИД,
|	ЕСТЬNULL(ВТ1.НоменклатураУИД, ВТ2.НоменклатураУИД) КАК НоменклатураУИД,
|	ЕСТЬNULL(ВТ1.ПартияУИД, ВТ2.ПартияУИД) КАК ПартияУИД,
|	ЕСТЬNULL(ВТ1.Количество1, 0) КАК Количество1,
|	ЕСТЬNULL(ВТ2.Количество2, 0) КАК Количество2
|ИЗ
|	ВТ_Таблица1 КАК ВТ1
|		ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Таблица2 КАК ВТ2
|		ПО ВТ1.ДокументУИД = ВТ2.ДокументУИД
|			И ВТ1.НоменклатураУИД = ВТ2.НоменклатураУИД
|			И ВТ1.ПартияУИД = ВТ2.ПартияУИД";

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

Преимущество метода: СУБД автоматически построит оптимальный план выполнения запроса (например, выберет Hash Match или Merge Join), что обеспечит колоссальную скорость обработки даже на миллионах записей. Однако помните, что передача таблиц значений в параметры запроса поддерживается только на сервере 1С.

Решение №5: Прямая стыковка наборов данных в СКД и ее подводные камни

В процессе обсуждения автору темы предложили отказаться от предварительного ручного соединения таблиц на встроенном языке и передать обе таблицы в СКД (Систему компоновки данных) как отдельные наборы данных-объекты, связав их непосредственно на вкладке «Связи наборов данных».

Это действительно лаконичное и удобное решение с точки зрения объема исходного кода. СКД сама возьмет на себя сопоставление строк. Однако здесь скрывается серьезная ловушка производительности, о которой должен знать каждый разработчик:

  1. Когда СКД связывает два набора данных, где один или оба набора являются внешними объектами (НаборДанных-Объект), платформа выполняет сопоставление записей последовательно на сервере приложений 1С.
  2. Для каждой строки основного набора данных СКД будет выполнять поиск во втором наборе. На больших массивах информации это превращается в тот самый неоптимальный медленный цикл со сложностью O(N*M).
  3. Если один из наборов данных формируется динамическим запросом к БД, а второй является внешней таблицей (из HTTP-запроса), СКД начнет выполнять к СУБД отдельные параметризованные запросы для каждой строки внешней таблицы. Это мгновенно перегрузит SQL-сервер и вызовет критическое зависание отчета.

Рекомендация: Соединяйте таблицы непосредственно в СКД только в том случае, если объем сопоставляемых данных невелик (до нескольких тысяч строк). Если таблицы содержат десятки и сотни тысяч записей, выполняйте их сопоставление и подготовку программно (например, методом свертки или через менеджер временных таблиц в одном запросе), а в СКД передавайте уже один готовый, плоский и предварительно сопоставленный набор данных.

Сводная таблица выбора метода сопоставления данных

Для закрепления материала проанализируем, в каких ситуациях какой метод сопоставления таблиц окажется наиболее эффективным:

  1. Объем данных до 10 000 строк: Отлично подойдет правильный составной индекс в ТаблицеЗначений или прямое связывание наборов данных в СКД.
  2. Объем от 10 000 до 100 000 строк: Лучшим выбором станет использование объекта Соответствие (хеширование по строковому ключу) или метод быстрой свертки («Знак»-свертка). Это позволит избежать накладных расходов на передачу данных в СУБД.
  3. Объем свыше 100 000 строк: Рекомендуется выполнять обработку через временные таблицы в Запросе, перенося всю вычислительную нагрузку на SQL-сервер.

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

← На главную