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