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