Загрузка данных из внешних файлов Excel — одна из самых частых задач, с которыми сталкивается разработчик 1С. На первый взгляд алгоритм выглядит просто: прочитать файл, найти товар в базе по наименованию или артикулу, а если его нет — создать новый. Однако на практике начинающие программисты часто совершают ошибки, которые приводят к появлению дублей в справочнике Номенклатура. Особенно часто это происходит из-за некорректного сравнения типов данных или неоптимальной структуры кода.
В этой статье мы подробно разберем, почему возникает дублирование товаров при загрузке из Excel, проанализируем типичные ошибки в коде запросов и рассмотрим лучшие практики по поиску и созданию элементов справочников в 1С.
Давайте подробно рассмотрим фрагмент кода, который часто используют новички для проверки существования номенклатуры в базе данных:
ПроверитьНоменклатуры = Строка(СокрЛП(ExcelПриложение.Cells(Строка, НоменклатураКол).Value));
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Номенклатура";
Запрос.УстановитьПараметр("Номенклатура", ПроверитьНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
Если НЕ Выборка.Следующий() Тогда
// Создание нового элемента номенклатуры
КонецЕсли;
Выясним причину, по которой этот код работает некорректно и всегда создает новые элементы справочника, даже если товар с таким названием уже существует в базе.
Обратите внимание на условие в запросе: Номенклатура.Ссылка = &Номенклатура. В левой части равенства находится поле Ссылка, которое имеет тип данных СправочникСсылка.Номенклатура. В правой части равенства в качестве параметра передается переменная ПроверитьНоменклатуры, имеющая тип Строка (поскольку значение считывается напрямую из ячейки Excel и преобразуется в строку).
Важное правило 1С: Платформа не выполняет неявное приведение типов в запросах. Сравнение уникального идентификатора ссылки со строкой текста всегда будет возвращать ложь (Ложь). В результате запрос всегда будет пустым, условие создания новой номенклатуры будет выполняться для каждой строчки файла, и в вашей базе начнут плодиться дубли товаров без привязки к группам.
Чтобы исправить эту ошибку, мы должны сравнивать текстовое поле справочника с текстовым параметром из Excel. Изменим текст запроса, обращаясь напрямую к реквизиту Наименование:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование = &Наименование";
Запрос.УстановитьПараметр("Наименование", ПроверитьНоменклатуры);
ТаблицаРезультатов = Запрос.Выполнить().Выгрузить();
Если ТаблицаРезультатов.Количество() = 0 Тогда
// Товар не найден, создаем новый
Иначе
// Товар найден, берем готовую ссылку
СсылкаНаТовар = ТаблицаРезультатов[0].Ссылка;
КонецЕсли;
Рассмотрим подробнее этот вариант. Теперь мы сравниваем Наименование (тип Строка) со строкой из Excel. Такой поиск отработает корректно и вернет ссылку на уже существующий товар.
Некоторые разработчики предлагают использовать оператор ПОДОБНО с маской поиска, например:
ГДЕ Номенклатура.Наименование ПОДОБНО &Наименование
// И передавать параметр как: ПроверитьНоменклатуры + "%"
Давайте проанализируем ситуацию, к которой это может привести. Допустим, в вашем прайс-листе Excel есть две позиции: "Экран" и "Экран Dell". Если в базе уже существует "Экран Dell", то при поиске простого "Экрана" по маске "Экран%" запрос найдет "Экран Dell" и вернет его ссылку. В итоге программа не создаст нужный товар "Экран", а вместо этого в документ дважды подставится "Экран Dell".
Вывод: Для точного поиска номенклатуры при загрузке прайс-листов всегда используйте строгое равенство (=), а не оператор ПОДОБНО.
Если вы не хотите писать сложные запросы, можно воспользоваться встроенным методом объектной модели 1С — НайтиПоНаименованию. Посмотрим на пример его реализации:
ИскомоеИмя = СокрЛП(ExcelПриложение.Cells(Строка, НоменклатураКол).Value);
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоНаименованию(ИскомоеИмя, Истина);
Если НайденнаяСсылка.Пустая() Тогда
// Создаем новый элемент справочника
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Наименование = ИскомоеИмя;
// Заполнение остальных реквизитов...
НовыйЭлемент.Записать();
НайденнаяСсылка = НовыйЭлемент.Ссылка;
КонецЕсли;
// Теперь мы можем использовать НайденнаяСсылка для добавления в табличную часть
Обратите внимание на второй параметр метода: НайтиПоНаименованию(ИскомоеИмя, Истина). Флаг Истина означает, что поиск должен быть **точным**. Если передать Ложь (или опустить этот параметр), 1С найдет элемент, наименование которого начинается с искомой строки, что опять же приведет к ошибкам сопоставления.
В исходном коде автора темы прослеживается еще одна архитектурная проблема: двойной поиск. Сначала выполняется запрос к базе для проверки наличия товара, а затем, если товар найден, повторно вызывается метод Справочники.Номенклатура.НайтиПоНаименованию. Это удваивает нагрузку на СУБД.
Правильный подход заключается в том, чтобы получить ссылку на найденный товар непосредственно из первого поиска и сразу использовать ее в дальнейшем алгоритме, не обращаясь к базе повторно.
Выполнение запросов или методов поиска внутри цикла чтения строк Excel — это классический антипаттерн (плохая практика программирования). Если в вашем файле Excel будет 10 000 строк, программа выполнит 10 000 отдельных обращений к базе данных. Это приведет к зависанию интерфейса и огромной нагрузке на сервер.
Давайте разберем по шагам, как оптимизировать этот процесс с помощью одной пакетной операции:
Номенклатура по наименованию (используя ЛЕВОЕ СОЕДИНЕНИЕ).Null.Рассмотрим пример реализации оптимизированного алгоритма:
// Шаг 1: Создаем Таблицу Значений в памяти для хранения данных из файла
ТЗ_Импорт = Новый ТаблицаЗначений;
ТЗ_Импорт.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150)));
ТЗ_Импорт.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТЗ_Импорт.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
Для Строка = 2 По ExcelПоследняяСтрока Цикл
ИмяТовара = СокрЛП(ExcelПриложение.Cells(Строка, НоменклатураКол).Value);
Если ПустаяСтрока(ИмяТовара) Тогда
Продолжить;
КонецЕсли;
НоваяСтрока = ТЗ_Импорт.Добавить();
НоваяСтрока.Наименование = ИмяТовара;
НоваяСтрока.Количество = ExcelПриложение.Cells(Строка, КоличествоКол).Value;
НоваяСтрока.Цена = ExcelПриложение.Cells(Строка, ЦенаКол).Value;
КонецЦикла;
// Шаг 2: Передаем таблицу в один запрос к базе данных
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ВременнаяТаблица.Наименование КАК Наименование,
| ВременнаяТаблица.Количество КАК Количество,
| ВременнаяТаблица.Цена КАК Цена,
| Номенклатура.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТЗ_Импорт КАК ВременнаяТаблица
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Данные.Наименование КАК Наименование,
| ВТ_Данные.Количество КАК Количество,
| ВТ_Данные.Цена КАК Цена,
| ЕСТЬNULL(Номенклатура.Ссылка, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Ссылка
|ИЗ
| ВТ_Данные КАК ВТ_Данные
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
| ПО ВТ_Данные.Наименование = Номенклатура.Наименование";
Запрос.УстановитьПараметр("ТЗ_Импорт", ТЗ_Импорт);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// Шаг 3: Обрабатываем результаты запроса
Для Каждого СтрИзФайла Из РезультатЗапроса Цикл
Если СтрИзФайла.Ссылка.Пустая() Тогда
// Создаем новый товар, так как в базе его нет
НовыйТовар = Справочники.Номенклатура.СоздатьЭлемент();
НовыйТовар.Наименование = СтрИзФайла.Наименование;
НовыйТовар.Записать();
ТоварСсылка = НовыйТовар.Ссылка;
Иначе
// Товар найден в базе без лишних запросов в цикле
ТоварСсылка = СтрИзФайла.Ссылка;
EndIf;
// Заполняем табличную часть документа полученной ссылкой
НоваяСтрокаДок = ТабличнаяЧастьДокумента.Добавить();
НоваяСтрокаДок.Номенклатура = ТоварСсылка;
НоваяСтрокаДок.Количество = СтрИзФайла.Количество;
НоваяСтрокаДок.Цена = СтрИзФайла.Цена;
КонецЦикла;
Этот способ работает в десятки раз быстрее обычного перебора строк, бережет ресурсы сервера и гарантирует отсутствие дублирования информации.
Использование COMОбъект("Excel.Application") постепенно уходит в прошлое. Этот метод имеет критические недостатки:
EXCEL.EXE в диспетчере задач при аварийном завершении работы скрипта, что приводит к утечкам оперативной памяти.В современной платформе 1С:Предприятие 8.3 для разбора файлов Excel рекомендуется использовать встроенный объект ТабличныйДокумент. Он умеет читать файлы .xls и .xlsx напрямую, без сторонних библиотек. Посмотрим, как это реализовать:
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
Сообщить("Не удалось прочитать файл! Ошибка: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Теперь мы можем обходить ячейки табличного документа, используя встроенные методы
Для Строка = 2 По ТабДок.ВысотаТаблицы Цикл
НаименованиеТовара = СокрЛП(ТабДок.ПолучитьОбласть("R" + Строка + "C" + НоменклатураКол).ТекущийОбласть.Текст);
// Дальнейшая обработка данных аналогична работе с Excel
КонецЦикла;
Использование этого метода решает проблемы кроссплатформенности и значительно ускоряет разбор файлов больших объемов.