В процессе ведения учета и интеграции баз данных пользователи часто сталкиваются с дублированием элементов справочника Номенклатура. При попытке программного обхода и переименования дублей типовой ошибкой является некорректная конкатенация строк, в результате которой вместо аккуратных названий с порядковыми номерами («Товар 1», «Товар 2», «Товар 3») формируются нечитаемые конструкции вида («Товар1», «Товар11», «Товар111»). Разберем подробно, почему возникает такая ошибка, как оптимизировать запрос для поиска дублей и какие алгоритмы нумерации являются наиболее надежными.
Рассмотрим типовой фрагмент кода, приводящий к ошибке умножения символов:
Если СопоставлениеИмен[Выборка.Наименование] = Неопределено Тогда
СопоставлениеИмен[Выборка.Наименование] = Выборка.Наименование;
Иначе
СопоставлениеИмен[Выборка.Наименование] = "" + СопоставлениеИмен[Выборка.Наименование] + "1";
Спр = Выборка.Ссылка.ПолучитьОбъект();
Спр.Наименование = СопоставлениеИмен[Выборка.Наименование];
Спр.Записать();
КонецЕсли;
Проанализируем причину такого поведения:
Соответствие в качестве значения записывается строка с полным наименованием."1"."Кукла1", на второй — "Кукла11", на третьей — "Кукла111".Для корректной работы необходимо хранить в коллекции не модифицированную строку, а числовой счетчик дубликатов, либо использовать алгоритм выделения числовой части из конца строки.
Разберем наиболее простое, быстрое и надежное решение. В объекте Соответствие в качестве ключа будем сохранять исходное наименование, а в качестве значения — целочисленный счетчик вхождений.
&НаСервереБезКонтекста
Процедура ПереименоватьНоменклатуруНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование В
| (ВЫБРАТЬ
| НоменклатураДубли.Наименование
| ИЗ
| Справочник.Номенклатура КАК НоменклатураДубли
| ГДЕ
| НЕ НоменклатураДубли.ЭтоГруппа
| И НЕ НоменклатураДубли.ПометкаУдаления
| СГРУППИРОВАТЬ ПО
| НоменклатураДубли.Наименование
| ИМЕЮЩИЕ
| КОЛИЧЕСТВО(НоменклатураДубли.Ссылка) > 1)
|
|УПОРЯДОЧИТЬ ПО
| Номенклатура.Наименование,
| Номенклатура.Код";
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат;
КонецЕсли;
СопоставлениеИмен = Новый Соответствие;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
КоличествоДублей = СопоставлениеИмен[Выборка.Наименование];
Если КоличествоДублей = Неопределено Тогда
// Первый элемент оставляем без изменений как оригинал (или задаем индекс 1)
СопоставлениеИмен.Вставить(Выборка.Наименование, 1);
Иначе
// Увеличиваем счетчик для последующих дублей
КоличествоДублей = КоличествоДублей + 1;
СопоставлениеИмен.Вставить(Выборка.Наименование, КоличествоДублей);
СпрОбъект = Выборка.Ссылка.ПолучитьОбъект();
СпрОбъект.ОбменДанными.Загрузка = Истина; // Отключаем лишние подписки и проверки при записи
// Формируем читаемое имя с суффиксом дубля
СпрОбъект.Наименование = СокрЛП(Выборка.Наименование) + " (Дубль " + Строка(КоличествоДублей) + ")";
СпрОбъект.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Преимущества данного подхода:
УПОРЯДОЧИТЬ ПО) остается эталонным и не меняет своего наименования.Дубль 2, Дубль 3 и т.д.ОбменДанными.Загрузка = Истина ускоряет запись и предотвращает ошибки при проведении валидации других реквизитов.Посмотрим на ситуацию, когда исходные данные уже были частично переименованы (например, в базе присутствуют позиции «Кукла 1», «Кукла 2») и требуется найти крайнее правое число, инкрементировать его и сформировать следующее уникальное наименование.
&НаСервереБезКонтекста
Функция ПолучитьСледующееНаименование(ИсходноеИмя)
СтрокаНаименования = СокрЛП(ИсходноеИмя);
ДлинаНаименования = СтрДлина(СтрокаНаименования);
СтрокаНомера = "";
// Двигаемся справа налево в поисках числовых символов
Пока ДлинаНаименования > 0 Цикл
Символ = Сред(СтрокаНаименования, ДлинаНаименования, 1);
// Проверяем, является ли символ цифрой от 0 до 9
Если Символ >= "0" И Символ <= "9" Тогда
СтрокаНомера = Символ + СтрокаНомера;
ДлинаНаименования = ДлинаНаименования - 1;
Иначе
Прервать;
КонецЕсли;
КонецЦикла;
Если ПустаяСтрока(СтрокаНомера) Тогда
СледующийНомер = 1;
БазовоеИмя = СтрокаНаименования;
Иначе
СледующийНомер = Число(СтрокаНомера) + 1;
БазовоеИмя = Лев(СтрокаНаименования, ДлинаНаименования);
КонецЕсли;
Возврат СокрЛП(БазовоеИмя) + " " + Формат(СледующийНомер, "ЧГ=0");
КонецФункции
Особенности работы строкового парсера:
Символ >= "0" И Символ <= "9" выполняется корректно на уровне строк без использования медленного блока Попытка ... Исключение.СтрокаНомера = Символ + СтрокаНомера.Формат(СледующийНомер, "ЧГ=0") исключает появление неразрывных пробелов при переходе через тысячные значения.В учетных системах добавление абстрактных цифр (1, 2, 3) может запутать менеджеров и складских работников, если товар сам по себе содержит числовые характеристики (например, «Болт М8х20» или «Кабель 3х1.5»). В таких случаях надежнее дополнять наименование кодом справочника или артикулом:
СпрОбъект = Выборка.Ссылка.ПолучитьОбъект();
СпрОбъект.ОбменДанными.Загрузка = Истина;
// Добавляем к наименованию уникальный системный код
СпрОбъект.Наименование = СокрЛП(Выборка.Наименование) + " [Код: " + СокрЛП(Выборка.Код) + "]";
СпрОбъект.Записать();
Проанализируем технические нюансы, которые необходимо соблюдать при пакетной обработке справочников:
Наименование имеет строгую длину (обычно 100 или 150 символов). Если исходное наименование занимает всю длину, добавление суффикса вызовет ошибку записи. Перед записью строку необходимо обрезать:
МаксДлина = 100;
Суффикс = " (Дубль " + Строка(КоличествоДублей) + ")";
ДлинаСуффикса = СтрДлина(Суффикс);
ИтоговоеИмя = Лев(СокрЛП(Выборка.Наименование), МаксДлина - ДлинаСуффикса) + Суффикс;
НачатьТранзакцию() и ЗафиксироватьТранзакцию() каждые 500–1000 записей.Поиск и удаление дублей, которая заменяет ссылки во всех документах базы на один выбранный эталонный элемент.