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