Как проверить наличие номенклатуры и избежать ошибки дублирования при загрузке из Excel в 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

При написании собственных обработок загрузки данных из внешних файлов (Excel, CSV, MXL) программисты часто сталкиваются с ситуацией: алгоритм проверки утверждает, что номенклатуры в базе нет, однако при попытке вызвать метод Записать() платформа генерирует исключение о том, что такая номенклатура уже зарегистрирована. Разберем детально, почему это происходит, в чем заключаются типичные ошибки исходного алгоритма и как правильно организовать поиск дублей в 1С.

Выясним причину возникновения ошибки дублирования

Проанализируем ситуацию: в типовых конфигурациях (включая «1С:Управление торговлей», в том числе для Беларуси) контроль уникальности элементов справочника Номенклатура выполняется на уровне обработчиков событий модуля объекта (ПередЗаписью). Ошибка «Такая номенклатура уже есть» возникает по следующим причинам:

  1. Встроенный контроль уникальности: в программе может быть активирована опция контроля уникальности рабочего наименования либо в карточке вида номенклатуры задано правило уникальности (например, по реквизиту Артикул или комбинации артикула и производителя). Если такое сочетание уже есть в базе данных (даже у элемента с пометкой на удаление), запись блокируется.
  2. Ограничение длины строки наименования: длина стандартного реквизита Наименование жестко зафиксирована в метаданных (например, 100 или 150 символов). Если в файле Excel название длиннее, то при строковом сравнении в коде оно считается новым. Однако при присвоении реквизиту платформа отсекает «хвост» строки, после чего наименование полностью совпадает с уже существующим в базе.
  3. Невидимые символы и неразрывные пробелы: из электронных таблиц часто считываются неразрывные пробелы (Символы.НПП), переносы строк и лишние пробельные символы по краям. В коде обычное условие равенства не срабатывает, но при записи срабатывают встроенные платформенные механизмы сопоставления.
  4. Повторы внутри самого исходного файла: если один и тот же товар упомянут в таблице Excel несколько раз, при первом проходе он создается, а на втором цикл вновь пытается создать дубль, так как повторная проверка не видит изменений без обновления выборки.

Анализ исходного кода и типичных ошибок

Посмотрим на исходный фрагмент проверки автора:


Запрос = Новый Запрос("ВЫБРАТЬ
                      | Номенклатура.Наименование,
                      | Номенклатура.Ссылка
                      |ИЗ
                      | Справочник.Номенклатура КАК Номенклатура");
Результат = Запрос.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
    Если Результат.Наименование = Строка.Получить(Объект.Наименование + 2) Тогда
        ДобавитьНовуюНоменклатуру = Ложь;
    КонецЕсли;
КонецЦикла;

В этом коде допущены критические архитектурные ошибки:

  1. Чтение всего справочника целиком: запрос выбирает абсолютно все записи из базы в память. При базе в десятки или сотни тысяч позиций такой алгоритм вызовет резкое падение производительности.
  2. Отсутствие нормализации строк: строки из Excel не очищаются от пробелов функцией СокрЛП() и не приводятся к единому регистру через ВРег().
  3. Не сбрасывается флаг: переменная ДобавитьНовуюНоменклатуру должна инициализироваться перед каждой итерацией проверки конкретной строки, иначе после первого найденного совпадения все последующие строки перестанут создаваться.

Способ 1. Использование менеджера справочника (быстрый точечный поиск)

Для простых сценариев загрузки небольших прайс-листов можно использовать встроенные методы менеджера справочника. Разберем по шагам, как выполнить поиск по наименованию или артикулу:


СтрокаНаименования = СокрЛП(СтрЗаменить(Строка.Получить(Объект.Наименование + 2), Символы.НПП, " "));
// Ограничиваем длину строки в соответствии с длиной реквизита в метаданных
ДлинаИмени = Метаданные.Справочники.Номенклатура.ДлинаНаименования;
СтрокаНаименования = Лев(СтрокаНаименования, ДлинаИмени);

// Выполняем точный поиск
НайденнаяНоменклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаНаименования, Истина);

Если НайденнаяНоменклатура.Пустая() Тогда
    // Товар не найден, создаем новый элемент
    НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
    НоменклатураОбъект.Наименование = СтрокаНаименования;
    // Заполнение остальных реквизитов...
    НоменклатураОбъект.Записать();
КонецЕсли;

Важный момент: метод НайтиПоНаименованию вторым параметром принимает булево значение точного соответствия. Передавая Истина, мы исключаем поиск по началу строки.

Способ 2. Параметризованный запрос к базе данных

Более надежный способ для проверки отдельных позиций — использование точечного запроса с отбором. Запрос позволяет выполнить регистронезависимое сравнение и исключить чтение лишней информации.


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |   Номенклатура.Ссылка КАК Ссылка
    |ИЗ
    |   Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |   Номенклатура.Наименование = &Наименование
    |   ИЛИ (Номенклатура.Артикул = &Артикул И &АртикулЗаполнен)";

Запрос.УстановитьПараметр("Наименование", СтрокаНаименования);
Запрос.УстановитьПараметр("Артикул", СокрЛП(Строка.Получить(Объект.Артикул + 2)));
Запрос.УстановитьПараметр("АртикулЗаполнен", Не ПустаяСтрока(СокрЛП(Строка.Получить(Объект.Артикул + 2))));

РезультатЗапроса = Запрос.Выполнить();

Если РезультатЗапроса.Пустой() Тогда
    // Элемент отсутствует в информационной базе, создаем новый
    НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
    // Заполнение и запись
КонецЕсли;

Способ 3. Пакетная проверка загружаемой таблицы (оптимальный вариант)

Если из файла прочитано большое количество строк (тысячи позиций), выполнять поиск или точечный запрос в цикле неэффективно. Рассмотрим наилучшую практику 1С: пакетную проверку через временные таблицы.

Разберем алгоритм по шагам:

  1. Считываем файл в объект ТаблицаЗначений.
  2. Удаляем дубликаты внутри самой таблицы с помощью метода Свернуть().
  3. Передаем всю таблицу значений в запрос как параметр.
  4. Связываем загружаемые данные левым соединением с существующим справочником Номенклатура.

// Предположим, что ТаблицаИзФайла содержит колонки "Наименование" и "Артикул"
// Сворачиваем дубли внутри файла перед поиском
ТаблицаИзФайла.Свернуть("Наименование, Артикул");

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТЗ.Наименование КАК Наименование,
    |   ТЗ.Артикул КАК Артикул
    |ПОМЕСТИТЬ ВТ_Загрузка
    |ИЗ
    |   &ТаблицаИзФайла КАК ТЗ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |   ВТ.Наименование КАК Наименование,
    |   ВТ.Артикул КАК Артикул
    |ИЗ
    |   ВТ_Загрузка КАК ВТ
    |   ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
    |       ПО ВТ.Наименование = Номенклатура.Наименование
    |ГДЕ
    |   Номенклатура.Ссылка ЕСТЬ NULL";

Запрос.УстановитьПараметр("ТаблицаИзФайла", ТаблицаИзФайла);
ТаблицаНовых = Запрос.Выполнить().Выгрузить();

// В ТаблицаНовых остались только те строки, которых гарантированно нет в базе
Для Каждого Стр Из ТаблицаНовых Цикл
    НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
    НоменклатураОбъект.Наименование = Стр.Наименование;
    НоменклатураОбъект.Артикул = Стр.Артикул;
    
    // Заполняем реквизиты и записываем в безопасном блоке
    Попытка
        НоменклатураОбъект.Записать();
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаНоменклатуры", УровеньЖурналаРегистрации.Ошибка,
            Метаданные.Справочники.Номенклатура,, ОписаниеОшибки());
    КонецПопытки;
КонецЦикла;

Обход прикладных проверок при начальном заполнении

Если выполняется первоначальное наполнение базы данных и требуется намеренно отключить прикладной контроль уникальности, реализованный в модуле объекта конфигурации, используют свойство ОбменДанными.Загрузка:


НоменклатураОбъект.ОбменДанными.Загрузка = Истина;
НоменклатураОбъект.Записать();

Обратите внимание: использование режима загрузки полностью отключает обработчики ПередЗаписью и ПриЗаписи. Это позволяет избежать блокировок со стороны конфигурации, но накладывает на разработчика ответственность за корректность заполнения всех обязательных реквизитов и регистров.

← На главную