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

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

Загрузка данных из внешних файлов Excel — одна из самых частых задач, с которыми сталкивается разработчик 1С. На первый взгляд алгоритм выглядит просто: прочитать файл, найти товар в базе по наименованию или артикулу, а если его нет — создать новый. Однако на практике начинающие программисты часто совершают ошибки, которые приводят к появлению дублей в справочнике Номенклатура. Особенно часто это происходит из-за некорректного сравнения типов данных или неоптимальной структуры кода.

В этой статье мы подробно разберем, почему возникает дублирование товаров при загрузке из Excel, проанализируем типичные ошибки в коде запросов и рассмотрим лучшие практики по поиску и созданию элементов справочников в 1С.

Анализ проблемы: почему появляются дубли?

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


ПроверитьНоменклатуры = Строка(СокрЛП(ExcelПриложение.Cells(Строка, НоменклатураКол).Value));

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

Запрос.УстановитьПараметр("Номенклатура", ПроверитьНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
Если НЕ Выборка.Следующий() Тогда
    // Создание нового элемента номенклатуры
КонецЕсли;

Выясним причину, по которой этот код работает некорректно и всегда создает новые элементы справочника, даже если товар с таким названием уже существует в базе.

Обратите внимание на условие в запросе: Номенклатура.Ссылка = &Номенклатура. В левой части равенства находится поле Ссылка, которое имеет тип данных СправочникСсылка.Номенклатура. В правой части равенства в качестве параметра передается переменная ПроверитьНоменклатуры, имеющая тип Строка (поскольку значение считывается напрямую из ячейки Excel и преобразуется в строку).

Важное правило 1С: Платформа не выполняет неявное приведение типов в запросах. Сравнение уникального идентификатора ссылки со строкой текста всегда будет возвращать ложь (Ложь). В результате запрос всегда будет пустым, условие создания новой номенклатуры будет выполняться для каждой строчки файла, и в вашей базе начнут плодиться дубли товаров без привязки к группам.

Решение 1: Исправление поиска в запросе по наименованию

Чтобы исправить эту ошибку, мы должны сравнивать текстовое поле справочника с текстовым параметром из Excel. Изменим текст запроса, обращаясь напрямую к реквизиту Наименование:


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

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

Если ТаблицаРезультатов.Количество() = 0 Тогда
    // Товар не найден, создаем новый
Иначе
    // Товар найден, берем готовую ссылку
    СсылкаНаТовар = ТаблицаРезультатов[0].Ссылка;
КонецЕсли;

Рассмотрим подробнее этот вариант. Теперь мы сравниваем Наименование (тип Строка) со строкой из Excel. Такой поиск отработает корректно и вернет ссылку на уже существующий товар.

Опасность использования оператора ПОДОБНО

Некоторые разработчики предлагают использовать оператор ПОДОБНО с маской поиска, например:


ГДЕ Номенклатура.Наименование ПОДОБНО &Наименование
// И передавать параметр как: ПроверитьНоменклатуры + "%"

Давайте проанализируем ситуацию, к которой это может привести. Допустим, в вашем прайс-листе Excel есть две позиции: "Экран" и "Экран Dell". Если в базе уже существует "Экран Dell", то при поиске простого "Экрана" по маске "Экран%" запрос найдет "Экран Dell" и вернет его ссылку. В итоге программа не создаст нужный товар "Экран", а вместо этого в документ дважды подставится "Экран Dell".

Вывод: Для точного поиска номенклатуры при загрузке прайс-листов всегда используйте строгое равенство (=), а не оператор ПОДОБНО.

Решение 2: Использование объектной техники поиска

Если вы не хотите писать сложные запросы, можно воспользоваться встроенным методом объектной модели 1С — НайтиПоНаименованию. Посмотрим на пример его реализации:


ИскомоеИмя = СокрЛП(ExcelПриложение.Cells(Строка, НоменклатураКол).Value);
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоНаименованию(ИскомоеИмя, Истина);

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

// Теперь мы можем использовать НайденнаяСсылка для добавления в табличную часть

Обратите внимание на второй параметр метода: НайтиПоНаименованию(ИскомоеИмя, Истина). Флаг Истина означает, что поиск должен быть **точным**. Если передать Ложь (или опустить этот параметр), 1С найдет элемент, наименование которого начинается с искомой строки, что опять же приведет к ошибкам сопоставления.

Оптимизация кода: избавляемся от лишних запросов

В исходном коде автора темы прослеживается еще одна архитектурная проблема: двойной поиск. Сначала выполняется запрос к базе для проверки наличия товара, а затем, если товар найден, повторно вызывается метод Справочники.Номенклатура.НайтиПоНаименованию. Это удваивает нагрузку на СУБД.

Правильный подход заключается в том, чтобы получить ссылку на найденный товар непосредственно из первого поиска и сразу использовать ее в дальнейшем алгоритме, не обращаясь к базе повторно.

Продвинутый уровень: Уход от "Запроса в цикле"

Выполнение запросов или методов поиска внутри цикла чтения строк Excel — это классический антипаттерн (плохая практика программирования). Если в вашем файле Excel будет 10 000 строк, программа выполнит 10 000 отдельных обращений к базе данных. Это приведет к зависанию интерфейса и огромной нагрузке на сервер.

Давайте разберем по шагам, как оптимизировать этот процесс с помощью одной пакетной операции:

  1. Прочитать все наименования из Excel и выгрузить их во временную Таблицу Значений.
  2. Передать эту таблицу в запрос в качестве параметра.
  3. В запросе соединить нашу временную таблицу со справочником Номенклатура по наименованию (используя ЛЕВОЕ СОЕДИНЕНИЕ).
  4. Получить готовый результат: список строк, где для существующих товаров уже проставлены ссылки, а для отсутствующих — значение Null.
  5. Создать недостающие товары и быстро заполнить итоговый документ.

Рассмотрим пример реализации оптимизированного алгоритма:


// Шаг 1: Создаем Таблицу Значений в памяти для хранения данных из файла
ТЗ_Импорт = Новый ТаблицаЗначений;
ТЗ_Импорт.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150)));
ТЗ_Импорт.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТЗ_Импорт.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));

Для Строка = 2 По ExcelПоследняяСтрока Цикл
    ИмяТовара = СокрЛП(ExcelПриложение.Cells(Строка, НоменклатураКол).Value);
    Если ПустаяСтрока(ИмяТовара) Тогда
        Продолжить;
    КонецЕсли;
    
    НоваяСтрока = ТЗ_Импорт.Добавить();
    НоваяСтрока.Наименование = ИмяТовара;
    НоваяСтрока.Количество = ExcelПриложение.Cells(Строка, КоличествоКол).Value;
    НоваяСтрока.Цена = ExcelПриложение.Cells(Строка, ЦенаКол).Value;
КонецЦикла;

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

Запрос.УстановитьПараметр("ТЗ_Импорт", ТЗ_Импорт);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();

// Шаг 3: Обрабатываем результаты запроса
Для Каждого СтрИзФайла Из РезультатЗапроса Цикл
    Если СтрИзФайла.Ссылка.Пустая() Тогда
        // Создаем новый товар, так как в базе его нет
        НовыйТовар = Справочники.Номенклатура.СоздатьЭлемент();
        НовыйТовар.Наименование = СтрИзФайла.Наименование;
        НовыйТовар.Записать();
        ТоварСсылка = НовыйТовар.Ссылка;
    Иначе
        // Товар найден в базе без лишних запросов в цикле
        ТоварСсылка = СтрИзФайла.Ссылка;
    EndIf;
    
    // Заполняем табличную часть документа полученной ссылкой
    НоваяСтрокаДок = ТабличнаяЧастьДокумента.Добавить();
    НоваяСтрокаДок.Номенклатура = ТоварСсылка;
    НоваяСтрокаДок.Количество = СтрИзФайла.Количество;
    НоваяСтрокаДок.Цена = СтрИзФайла.Цена;
КонецЦикла;

Этот способ работает в десятки раз быстрее обычного перебора строк, бережет ресурсы сервера и гарантирует отсутствие дублирования информации.

Современная альтернатива COM-объектам Excel

Использование COMОбъект("Excel.Application") постепенно уходит в прошлое. Этот метод имеет критические недостатки:

В современной платформе 1С:Предприятие 8.3 для разбора файлов Excel рекомендуется использовать встроенный объект ТабличныйДокумент. Он умеет читать файлы .xls и .xlsx напрямую, без сторонних библиотек. Посмотрим, как это реализовать:


ТабДок = Новый ТабличныйДокумент;
Попытка
    ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
    Сообщить("Не удалось прочитать файл! Ошибка: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

// Теперь мы можем обходить ячейки табличного документа, используя встроенные методы
Для Строка = 2 По ТабДок.ВысотаТаблицы Цикл
    НаименованиеТовара = СокрЛП(ТабДок.ПолучитьОбласть("R" + Строка + "C" + НоменклатураКол).ТекущийОбласть.Текст);
    // Дальнейшая обработка данных аналогична работе с Excel
КонецЦикла;

Использование этого метода решает проблемы кроссплатформенности и значительно ускоряет разбор файлов больших объемов.

← На главную