Как эффективно найти дубли в таблице значений по части строки

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

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

Метод 1: Использование объекта Соответствие (Хеш-таблица)

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

Разберем этот алгоритм по шагам:

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

Посмотрим на пример кода, реализующий этот подход:


// Допустим, ТЗ_Данные - наша исходная таблица значений
НайденныеДубли = Новый Массив;
КэшЗначений = Новый Соответствие;

Для Каждого СтрокаТЗ Из ТЗ_Данные Цикл
    // Выделяем 5 символов справа для сравнения
    КлючПоиска = Прав(СокрЛП(СтрокаТЗ.Номер), 5);
    
    // Ищем, встречался ли нам такой ключ ранее
    СуществующаяЗапись = КэшЗначений.Получить(КлючПоиска);
    
    Если СуществующаяЗапись = Неопределено Тогда
        // Это первое вхождение, запоминаем строку
        КэшЗначений.Вставить(КлючПоиска, СтрокаТЗ);
    Иначе
        // Мы нашли дубль! 
        // Добавляем текущую строку в список дублей
        НайденныеДубли.Добавить(СтрокаТЗ);
        
        // Если нужно собрать ВСЕ строки (включая самую первую),
        // можно проверять тип значения или хранить в соответствии массив
        Если ТипЗнч(СуществующаяЗапись) <> Тип("Массив") Тогда
            НайденныеДубли.Добавить(СуществующаяЗапись);
            // Помечаем, что оригинал уже добавлен в результат
            КэшЗначений.Вставить(КлючПоиска, Новый Массив); 
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Важный нюанс: Почему мы используем Соответствие, а не Структура? Объект Структура в 1С имеет ограничение на именование ключей (они должны соответствовать правилам идентификаторов), в то время как Соответствие позволяет использовать в качестве ключа любую строку, включая те, что начинаются с цифр или содержат спецсимволы.

Метод 2: Выгрузка во временную таблицу и использование запроса

Этот способ часто вызывает споры среди разработчиков из-за нагрузки на сервер СУБД, однако он остается одним из самых простых в реализации. Проанализируем ситуацию: если таблица значений уже находится на сервере, выгрузка её в менеджер временных таблиц происходит достаточно быстро.

Алгоритм действий следующий:

  1. Создаем запрос (используя продвинутую консоль запросов) и передаем в него таблицу значений как параметр.
  2. Помещаем ТЗ во временную таблицу. При этом желательно заранее добавить в ТЗ колонку с результатом функции Прав, так как в самом языке запросов 1С функция ПРАВО (RIGHT) может не поддерживаться для некоторых типов полей (что часто требует сравнения строки с представлением ссылки) или СУБД внутри временных таблиц (в зависимости от версии платформы).
  3. Выполняем группировку по ключевому полю и используем условие ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1.

Пример реализации запросом (для отладки таких алгоритмов удобно использовать консоль запросов для управляемых форм):


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

Запрос = Новый Запрос;
Запрос.МенеджерВременных Таблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ТЗ КАК ТЗ;
    |ВЫБРАТЬ КлючДублей ИЗ ВТ_Данные 
    |СГРУППИРОВАТЬ ПО КлючДублей 
    |ИМЕЮЩИЕ КОЛИЧЕСТВО(КлючДублей) > 1";
    
Запрос.УстановитьПараметр("ТЗ", ТЗ_Данные);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// В РезультатЗапроса теперь список ключей, которые дублируются

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

Метод 3: Сортировка и последовательный обход

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

Проанализируем шаги реализации:

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

Посмотрим на пример кода с корректной обработкой "первого" дубля:


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

// 2. Сортировка
ТЗ_Данные.Сортировать("Суффикс");

// 3. Поиск
ПредыдущаяСтрока = Неопределено;
СписокДублей = Новый Массив;

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

Метод 4: Группировка через метод Свернуть

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

Алгоритм действий:

  1. Копируем исходную таблицу в новую переменную.
  2. Добавляем колонку Количество и заполняем её единицей для каждой строки.
  3. Сворачиваем таблицу по колонке с ключом дублей, суммируя колонку Количество.
  4. Удаляем из таблицы строки, где Количество = 1.

ТЗ_Копия = ТЗ_Данные.Скопировать();
ТЗ_Копия.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТЗ_Копия.ЗаполнитьЗначения(1, "Количество");

// Предполагаем, что колонка "Суффикс" уже заполнена
ТЗ_Копия.Свернуть("Суффикс", "Количество");

// Теперь в ТЗ_Копия только уникальные суффиксы и их количество
// Удалим те, что не являются дублями
Индекс = ТЗ_Копия.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если ТЗ_Копия[Индекс].Количество == 1 Тогда
        ТЗ_Копия.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Сравнение производительности и рекомендации

Рассмотрим технические особенности каждого подхода, чтобы понимать, когда какой метод использовать:

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

Подводя итог, для разовой задачи по поиску дублей в небольшой таблице значений (до 10-20 тысяч строк) наиболее простым и эффективным будет использование Соответствия. Это позволит избежать лишних обращений к СУБД и обеспечит высокую скорость выполнения кода.

← На главную