При работе в крупных информационных базах 1С часто возникает ситуация, когда в справочниках появляются дублирующиеся элементы (например, две карточки одной и той же номенклатуры или характеристики). Стандартные или сторонние обработки «Поиск и замена значений» при попытке объединить такие элементы часто сталкиваются с критической ошибкой. Проблема заключается в том, что в регистрах сведений (таких как Цены номенклатуры, Штрихкоды или Сведения о номенклатуре) после замены ссылки образуются записи с одинаковыми измерениями. Платформа 1С:Предприятие не позволяет записывать такие данные, выдавая ошибку неуникальности.
В этой статье мы подробно разберем, как программно обойти это ограничение, проанализируем примеры кода и рассмотрим подходы, используемые в продвинутых инструментах разработки.
Рассмотрим стандартную ситуацию. У нас есть две номенклатурные позиции: Товар А (дубль) и Товар А (оригинал). Для каждой из них в регистре сведений установлена цена. При замене дублей обработка пытается заменить ссылку в регистре. В результате в одном наборе записей оказываются две строки для Товара А (оригинала) с одинаковыми измерениями (например, Номенклатура, Характеристика, Склад). При попытке метода Записать() платформа проверяет уникальность ключа записи и, обнаружив дублирование, прерывает операцию.
Если нам нужно быстро решить проблему в рамках конкретной обработки, мы можем внедрить проверку набора записей непосредственно перед его записью в базу данных. Разберем алгоритм: мы создаем соответствие, в котором будем хранить уже встреченные ключи записей, и удаляем из набора те строки, которые пытаются записаться повторно.
Проанализируем пример кода, который можно встроить в процедуру замены ссылок:
// Только для регистров сведений, регистры накопления не трогаем,
// так как там дубли измерений допустимы и важны для итогов
Если Метаданные.РегистрыСведений.Содержит(НаборЗаписей.Метаданные()) И
НаборЗаписей.Метаданные().Измерения.Найти("Номенклатура") <> Неопределено И
НаборЗаписей.Метаданные().Измерения.Найти("Характеристика") <> Неопределено Тогда
// Создаем коллекцию для отслеживания уникальности
Соо = Новый Соответствие();
КУдалению = Новый Массив();
Для Каждого МЗ ИЗ НаборЗаписей Цикл
// Формируем уникальный ключ на основе УИД ссылок
КлючЗаписи = "" + МЗ.Номенклатура.УникальныйИдентификатор() + МЗ.Характеристика.УникальныйИдентификатор();
Если Соо[КлючЗаписи] <> Неопределено Тогда
// Если такой ключ уже был, помечаем строку на удаление
КУдалению.Добавить(МЗ);
КонецЕсли;
Соо.Вставить(КлючЗаписи, Истина);
КонецЦикла;
// Безжалостно удаляем дубли в наборе перед сохранением
Для Каждого МЗ ИЗ КУдалению Цикл
НаборЗаписей.Удалить(МЗ);
КонецЦикла;
КонецЕсли;
Важное замечание: этот метод «грубый», так как он просто оставляет первую попавшуюся запись и удаляет последующие. В случае с ценами это может привести к тому, что актуальная цена будет заменена старой. Однако для многих технических регистров (например, соответствия кодов в обменах) это вполне рабочее решение.
Продвинутые инструменты, такие как подсистема «ИР» (Инструменты разработчика), используют более универсальный подход. Вместо жесткой привязки к именам полей Номенклатура или Характеристика, система динамически анализирует метаданные регистра и определяет состав его уникального ключа.
Разберем, как это реализовано логически:
Рассмотрим фрагмент кода, реализующий такой универсальный поиск дублей внутри набора записей:
Если ТипТаблицы = "РегистрСведений" Тогда
Если СтруктураНабораЗаписей.Данные.Количество() > 1 Тогда
// Получаем имена всех полей, входящих в ключ (измерения + период для периодических)
СтрокаПолейКлюча = ирОбщий.СтрокаПолейКлючаТаблицыЛкс(ИмяТаблицыРегистра);
// Находим строки, которые стали дублями после замены ссылок
НеуникальныеКлючи = ирОбщий.НеуникальныеКлючиТаблицыЛкс(СтруктураНабораЗаписей.Данные, СтрокаПолейКлюча);
Если НеуникальныеКлючи.Количество() > 0 Тогда
ТаблицаНабора = СтруктураНабораЗаписей.Методы.Выгрузить();
Для Каждого НеуникальныйКлюч Из НеуникальныеКлючи Цикл
СтрокиНеуникальногоКлюча = ТаблицаНабора.НайтиСтроки(НеуникальныйКлюч);
// Оставляем только последнюю строку (индекс Количество - 1), остальные удаляем
Для ИндексУдаляемойСтроки = 0 По СтрокиНеуникальногоКлюча.Количество() - 2 Цикл
ТаблицаНабора.Удалить(СтрокиНеуникальногоКлюча[ИндексУдаляемойСтроки]);
КонецЦикла;
КонецЦикла;
СтруктураНабораЗаписей.Методы.Загрузить(ТаблицаНабора);
КонецЕсли;
КонецЕсли;
КонецЕсли;
Замена ссылок влияет не только на регистры, но и на табличные части документов. Если в одном заказе были строки с Товаром А (дубль) и Товаром А (оригинал), то после обработки в документе появятся две строки с абсолютно одинаковой номенклатурой. Это может привести к ошибкам при проведении (например, если в конфигурации стоит проверка на уникальность строк ТЧ).
Выясним, как это лечится. Профессиональные «заменялки» после замены ссылок в объекте вызывают метод Свернуть() для всех табличных частей. Однако делать это нужно осторожно. Если в строках различаются цены, счета учета или склады, «слепое» сворачивание приведет к потере данных. Мы рекомендуем перед сворачиванием проверять, совпадают ли во всех дублирующихся строках остальные значимые реквизиты.
Проанализируем ситуацию: в базе 100 000 ссылок на дубль. Если на каждую ссылку мы будем открывать, изменять и записывать объект заново — процесс затянется на часы. Продвинутые алгоритмы работают по следующей схеме:
Это позволяет сократить количество операций записи в базу данных в десятки раз. Использование Расширений для таких инструментов (как в случае с «Инструментами разработчика») позволяет внедрять эти алгоритмы без изменения основной конфигурации, что крайне удобно для типовых решений.
Подводя итог, можно сказать, что «умная» замена дублей — это не просто вызов функции ЗаменитьСсылку(), а комплексный процесс анализа данных. Для качественной очистки базы следует:
НаборЗаписей от дублирующихся ключей перед вызовом Записать().Соблюдение этих правил позволит вам избежать мучительного ручного исправления ошибок после работы автоматических инструментов и обеспечит чистоту данных в вашей системе 1С.