Как правильно переименовать дубли номенклатуры с добавлением порядкового номера в 1С?

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

В процессе ведения учета и интеграции баз данных пользователи часто сталкиваются с дублированием элементов справочника Номенклатура. При попытке программного обхода и переименования дублей типовой ошибкой является некорректная конкатенация строк, в результате которой вместо аккуратных названий с порядковыми номерами («Товар 1», «Товар 2», «Товар 3») формируются нечитаемые конструкции вида («Товар1», «Товар11», «Товар111»). Разберем подробно, почему возникает такая ошибка, как оптимизировать запрос для поиска дублей и какие алгоритмы нумерации являются наиболее надежными.

Анализ исходной проблемы: почему склеиваются единицы

Рассмотрим типовой фрагмент кода, приводящий к ошибке умножения символов:


Если СопоставлениеИмен[Выборка.Наименование] = Неопределено Тогда 
    СопоставлениеИмен[Выборка.Наименование] = Выборка.Наименование; 
Иначе 
    СопоставлениеИмен[Выборка.Наименование] = "" + СопоставлениеИмен[Выборка.Наименование] + "1"; 
    Спр = Выборка.Ссылка.ПолучитьОбъект(); 
    Спр.Наименование = СопоставлениеИмен[Выборка.Наименование]; 
    Спр.Записать(); 
КонецЕсли;

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

  1. В объект Соответствие в качестве значения записывается строка с полным наименованием.
  2. При обнаружении следующего дубля выполняется строковое сложение: к уже существующей строке приписывается символ "1".
  3. На первой итерации дубль получает имя "Кукла1", на второй — "Кукла11", на третьей — "Кукла111".

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

Способ 1. Использование числового счетчика в объекте Соответствие (рекомендуемый)

Разберем наиболее простое, быстрое и надежное решение. В объекте Соответствие в качестве ключа будем сохранять исходное наименование, а в качестве значения — целочисленный счетчик вхождений.


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

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

  1. Первый найденный элемент (например, с наименьшим кодом благодаря сортировке УПОРЯДОЧИТЬ ПО) остается эталонным и не меняет своего наименования.
  2. Последующие элементы нумеруются строго по порядку: Дубль 2, Дубль 3 и т.д.
  3. Использование флага ОбменДанными.Загрузка = Истина ускоряет запись и предотвращает ошибки при проведении валидации других реквизитов.

Способ 2. Посимвольный разбор строки и инкремент суффикса

Посмотрим на ситуацию, когда исходные данные уже были частично переименованы (например, в базе присутствуют позиции «Кукла 1», «Кукла 2») и требуется найти крайнее правое число, инкрементировать его и сформировать следующее уникальное наименование.


&НаСервереБезКонтекста
Функция ПолучитьСледующееНаименование(ИсходноеИмя)
    
    СтрокаНаименования = СокрЛП(ИсходноеИмя);
    ДлинаНаименования  = СтрДлина(СтрокаНаименования);
    СтрокаНомера       = "";
    
    // Двигаемся справа налево в поисках числовых символов
    Пока ДлинаНаименования > 0 Цикл
        Символ = Сред(СтрокаНаименования, ДлинаНаименования, 1);
        
        // Проверяем, является ли символ цифрой от 0 до 9
        Если Символ >= "0" И Символ <= "9" Тогда
            СтрокаНомера = Символ + СтрокаНомера;
            ДлинаНаименования = ДлинаНаименования - 1;
        Иначе
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    Если ПустаяСтрока(СтрокаНомера) Тогда
        СледующийНомер = 1;
        БазовоеИмя = СтрокаНаименования;
    Иначе
        СледующийНомер = Число(СтрокаНомера) + 1;
        БазовоеИмя = Лев(СтрокаНаименования, ДлинаНаименования);
    КонецЕсли;
    
    Возврат СокрЛП(БазовоеИмя) + " " + Формат(СледующийНомер, "ЧГ=0");
    
КонецФункции

Особенности работы строкового парсера:

  1. Сравнение символов Символ >= "0" И Символ <= "9" выполняется корректно на уровне строк без использования медленного блока Попытка ... Исключение.
  2. Формирование строки номера происходит в правильном порядке разрядов: СтрокаНомера = Символ + СтрокаНомера.
  3. Конструкция Формат(СледующийНомер, "ЧГ=0") исключает появление неразрывных пробелов при переходе через тысячные значения.

Способ 3. Уникализация через системный код или артикул

В учетных системах добавление абстрактных цифр (1, 2, 3) может запутать менеджеров и складских работников, если товар сам по себе содержит числовые характеристики (например, «Болт М8х20» или «Кабель 3х1.5»). В таких случаях надежнее дополнять наименование кодом справочника или артикулом:


СпрОбъект = Выборка.Ссылка.ПолучитьОбъект();
СпрОбъект.ОбменДанными.Загрузка = Истина;

// Добавляем к наименованию уникальный системный код
СпрОбъект.Наименование = СокрЛП(Выборка.Наименование) + " [Код: " + СокрЛП(Выборка.Код) + "]";
СпрОбъект.Записать();

Важные практические рекомендации при массовом переименовании

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

  1. Контроль максимальной длины реквизита: Реквизит Наименование имеет строгую длину (обычно 100 или 150 символов). Если исходное наименование занимает всю длину, добавление суффикса вызовет ошибку записи. Перед записью строку необходимо обрезать:
    
    МаксДлина = 100;
    Суффикс = " (Дубль " + Строка(КоличествоДублей) + ")";
    ДлинаСуффикса = СтрДлина(Суффикс);
        
    ИтоговоеИмя = Лев(СокрЛП(Выборка.Наименование), МаксДлина - ДлинаСуффикса) + Суффикс;
    
  2. Использование транзакций: При обработке больших массивов данных (от 5000 элементов) рекомендуется делить запись на порции с явным вызовом НачатьТранзакцию() и ЗафиксироватьТранзакцию() каждые 500–1000 записей.
  3. Слияние вместо переименования: Простое переименование устраняет визуальную одинаковость, но оставляет дубли в документах и регистрах, расщепляя остатки. Для полного устранения дублирования рекомендуется использовать типовую обработку БСП Поиск и удаление дублей, которая заменяет ссылки во всех документах базы на один выбранный эталонный элемент.
← На главную