Как загрузить данные из Excel в 1С 8.2 и эффективно свернуть дубли с объединением строк

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

При загрузке данных из внешних источников, таких как файлы Excel, часто возникает задача не просто импортировать строки, но и выполнить их «умную» свертку. Проблема обостряется, когда объем данных велик (например, более 30 000 строк), а стандартный метод Свернуть() объекта ТаблицаЗначений не подходит из-за необходимости объединять текстовые поля (адреса, телефоны) через разделитель, исключая при этом повторы внутри самих строк. Разберем, как превратить медленную обработку, длящуюся сутками, в быстрый и надежный алгоритм.

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

Рассмотрим типичную ошибку начинающего разработчика. Для удаления дублей часто используется алгоритм с вложенным обходом таблицы. В первом цикле берется строка, а во втором — происходит поиск всех похожих строк в этой же таблице. С точки зрения математики, сложность такого алгоритма составляет O(n²). Если в файле 30 000 строк, программе придется выполнить около 900 миллионов операций сравнения. Именно поэтому обработка может занимать несколько суток.

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

Решение 1: Оптимизированный обход с использованием метода «Найти»

Вместо того чтобы сравнивать каждую строку с каждой, мы будем использовать метод «одного прохода». Создадим результирующую таблицу значений и при обработке каждой строки из Excel будем проверять: есть ли уже такой контрагент в нашей новой таблице? Если есть — дополняем данные, если нет — создаем новую запись.

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


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

Для Каждого СтрИзФайла Из ТЗ_Исходная Цикл
    // Ищем контрагента в результирующей таблице
    ПараметрыПоиска = Новый Структура("Контрагент", СтрИзФайла.Контрагент);
    НайденныеСтроки = ТЗ_Результат.НайтиСтроки(ПараметрыПоиска);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        ТекСтр = НайденныеСтроки[0];
        // Суммируем количество
        ТекСтр.КоличествоОбращений = ТекСтр.КоличествоОбращений + СтрИзФайла.КоличествоОбращений;
        
        // Добавляем телефон, если его еще нет в строке
        Если ЗначениеЗаполнено(СтрИзФайла.Телефон) И Найти(ТекСтр.Телефон, СтрИзФайла.Телефон) = 0 Тогда
            ТекСтр.Телефон = ТекСтр.Телефон + ", " + СтрИзФайла.Телефон;
        КонецЕсли;
        
        // Добавляем адрес по тому же принципу
        Если ЗначениеЗаполнено(СтрИзФайла.Адрес) И Найти(ТекСтр.Адрес, СтрИзФайла.Адрес) = 0 Тогда
            ТекСтр.Адрес = ТекСтр.Адрес + ", " + СтрИзФайла.Адрес;
        КонецЕсли;
    Иначе
        // Если не нашли — добавляем новую строку
        НоваяСтр = ТЗ_Результат.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтр, СтрИзФайла);
    КонецЕсли;
КонецЦикла;

Важный момент: использование метода ТЗ_Результат.Индексы.Добавить("Контрагент") критически важно. Без индекса поиск методом НайтиСтроки будет замедляться пропорционально росту результирующей таблицы.

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

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

Выясним причину эффективности этого метода: в качестве ключа соответствия мы будем использовать уникальный идентификатор контрагента (в данном случае ФИО), а в качестве значения — саму строку ТаблицыЗначений.

Разберем по шагам реализацию:

  1. Создаем пустую ТаблицуЗначений для результата.
  2. Создаем объект Соответствие.
  3. В цикле по данным из Excel проверяем наличие ключа в соответствии.
  4. Если ключ найден, получаем прямую ссылку на строку таблицы и модифицируем её.

ТаблицаРезультат = Новый ТаблицаЗначений;
ТаблицаРезультат.Колонки.Добавить("Контрагент");
ТаблицаРезультат.Колонки.Добавить("Телефон");
ТаблицаРезультат.Колонки.Добавить("Адрес");
ТаблицаРезультат.Колонки.Добавить("КоличествоОбращений");

КартаКонтрагентов = Новый Соответствие;

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

Дополнительная очистка данных (Нормализация)

Зачастую данные в Excel «грязные»: телефоны могут быть записаны через «+7», «8» или со скобками. Чтобы свертка прошла успешно, проанализируем ситуацию и добавим предварительную очистку. Для этого удобно использовать функции работы со строками или Регулярные выражения через COM-объект VBScript.RegExp.

Рассмотрим простую функцию для нормализации телефона:


Функция ОчиститьТелефон(Знач ИсходныйТелефон)
    Результат = "";
    Для Поз = 1 По СтрДлина(ИсходныйТелефон) Цикл
        Символ = Сред(ИсходныйТелефон, Поз, 1);
        Если Найти("0123456789", Символ) > 0 Тогда
            Результат = Результат + Символ;
        КонецЕсли;
    КонецЦикла;
    Возврат Результат;
КонецФункции

Использование такой функции перед сравнением позволит избежать дублей вида «8900...» и «+7900...», так как они превратятся в одинаковую последовательность цифр.

Особенности записи в справочник

После того как данные в таблице значений свернуты, их необходимо записать в справочник. Чтобы этот процесс не стал «бутылочным горлышком», используйте транзакции. Запись 15 000 уникальных элементов по отдельности может занять много времени из-за постоянного обращения к диску.

Применим пакетную запись:


НачатьТранзакцию();
Счетчик = 0;
Для Каждого Стр Из ТаблицаРезультат Цикл
    НовыйЭлемент = Справочники.Контрагенты.СоздатьЭлемент();
    НовыйЭлемент.Наименование = Стр.Контрагент;
    // Заполнение остальных реквизитов...
    Попытка
        НовыйЭлемент.Записать();
        Счетчик = Счетчик + 1;
    Исключение
        Сообщить("Ошибка при записи: " + ОписаниеОшибки());
    КонецПопытки;
    
    // Фиксируем транзакцию каждые 1000 элементов, чтобы не перегружать лог
    Если Счетчик % 1000 = 0 Тогда
        ЗафиксироватьТранзакцию();
        НачатьТранзакцию();
    КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();

Резюме: Для быстрой обработки больших файлов Excel в 1С 8.2 следует отказаться от вложенных циклов в пользу объекта Соответствие или индексированных таблиц. Это позволяет сократить время выполнения задачи с нескольких суток до считанных минут.

← На главную