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

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

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

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

Причина №1: Почему «исчезают» коды и откуда берутся невидимые пробелы

Разберем типичную ситуацию. В коде обработки прописано чтение ячейки Excel и последующий поиск элемента:


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

В отладчике код выглядит как 47 808 550, в то время как в справочнике 1С этот же код записан как 47808550. Попытки применить метод СтрЗаменить(КодТовара, " ", "") ни к чему не приводят — пробелы остаются на месте, а функция поиска стабильно возвращает «Товар не найден».

Выясним причину этого поведения. Она кроется в автоматическом преобразовании типов платформой 1С:

  1. Если в Excel ячейка имеет числовой формат, то при чтении через COM-объект 1С интерпретирует считанное значение как тип Число.
  2. Когда мы принудительно преобразуем число в строку с помощью конструкции Строка(КодТовара), платформа применяет к нему стандартное региональное форматирование. По умолчанию для чисел в 1С настроена группировка триад (разделение тысяч).
  3. В качестве разделителя групп платформа использует не обычный пробел (код символа 32), а неразрывный пробел (символ с кодом 160, или предопределенная константа Символы.НПП).

Именно поэтому стандартная замена обычного пробела СтрЗаменить(СтрокаТовара, " ", "") не дает никакого результата — она просто не находит нужный символ в строке.

Решение проблемы с пробелами в кодах товаров

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

Способ 1. Использование функции Формат (Рекомендуемый)

Самый изящный способ — сразу форматировать число при преобразовании в строку, явно указав платформе, что группировка не требуется (параметр ЧГ=0):


КодИзExcel = Excel.Cells(СчСтроки, НомерКолонкиТовара).Value;
СтрокаКода = Формат(КодИзExcel, "ЧГ=0");
// Теперь СтрокаКода гарантированно содержит "47808550" вместо "47 808 550"

Способ 2. Явное удаление неразрывных пробелов

Если в коде по какой-то причине уже получена строка с разделителями, мы можем принудительно удалить из нее все неразрывные пробелы:


ТекущийКод = Строка(КодТовара);
// Удаляем неразрывные пробелы
ТекущийКод = СтрЗаменить(ТекущийКод, Символы.НПП, "");
// Дополнительно страхуемся от обычных пробелов
ТекущийКод = СтрЗаменить(ТекущийКод, " ", "");

Использование любого из этих вариантов позволит функции НайтиПоКоду() успешно сопоставить строку с кодом в базе данных.

Причина №2: Ошибка сравнения ссылочных типов с примитивными (на примере Валюты)

Рассмотрим вторую проблему, часто возникающую при сопоставлении реквизитов документов. Допустим, в Excel есть колонка «Валюта», принимающая числовые значения (1 — доллар, 2 — сум). Разработчик пытается сопоставить эти данные с перечислением в 1С:


Функция ПолучитьВалюту(Вал) Экспорт
    Если Вал = 1 Тогда
        Возврат Перечисления.Валюты.Доллар;
    КонецЕсли;
    Если Вал = 2 Тогда
        Возврат Перечисления.Валюты.Сум;
    КонецЕсли;
КонецФункции

// В процедуре обработки:
Валюта = СокрЛП(Excel.Cells(СчСтроки, НомерКолонкиВалюты).Value);
Если Валюта = ОбщегоНазначения.ПолучитьВалюту(1) Тогда
    РаботаСДокументами.РасчетСуммыСКурсом(НоваяСтрока, КурсДоллара);
КонецЕсли;

При выполнении этого кода сумма расчетов в отладчике всегда равна нулю, так как условие внутри Если ... Тогда игнорируется. Проанализируем, почему это происходит.

Переменная Валюта хранит строковое значение "1" (или число 1), полученное из Excel. В то же время функция ПолучитьВалюту(1) возвращает объект типа ПеречислениеСсылка.Валюты (в данном случае — Перечисления.Валюты.Доллар). Платформа 1С не может напрямую сравнить ссылку на объект базы данных и примитивный тип (строку или число). Такое сравнение всегда возвращает значение Ложь.

Решение проблемы сопоставления типов

Чтобы сравнение отрабатывало корректно, нам необходимо сравнивать однотипные данные. Перепишем логику сопоставления. Сначала получим числовое значение из Excel, а затем сравним ссылки:


// Приводим считанное из Excel значение к числу
ЗначениеВалютыExcel = Число(Excel.Cells(СчСтроки, НомерКолонкиВалюты).Value);

// Получаем ссылку на элемент перечисления в 1С
ВыбраннаяВалюта = ПолучитьВалюту(ЗначениеВалютыExcel);

// Теперь сравниваем ссылку со ссылкой
Если ВыбраннаяВалюта = Перечисления.Валюты.Доллар Тогда
    РаботаСДокументами.РасчетСуммыСКурсом(НоваяСтрока, КурсДоллара);
КонецЕсли;

Критическая оптимизация: избавляемся от «Запроса в цикле»

Обратим внимание на архитектурную ошибку в исходном коде процедуры ОбработкаExcel. В цикле обхода строк файла для каждого товара вызывается серверная функция поиска:


Для СчСтроки = 2 По ФайлСтрок Цикл
    ...
    ПолучитьТовар = НайтиТоварПоКоду(СтрокаТовар);
    ...
КонецЦикла;

Под капотом функции НайтиПоКоду() платформа выполняет прямой SQL-запрос к базе данных. Если в загружаемом файле содержится 5 000 строк, система совершит 5 000 отдельных запросов к СУБД. Это классический антипаттерн «запрос в цикле», который приводит к лавинообразному падению производительности и зависанию сеанса пользователя.

Разберем правильный, профессиональный подход к оптимизации этой операции. Вместо поиска каждого товара по отдельности выполним сопоставление пакетно:

  1. Создадим массив или таблицу значений и соберем туда все коды из файла Excel.
  2. Одним запросом выберем из базы данных всю номенклатуру, коды которой входят в собранный массив.
  3. Результат запроса поместим в коллекцию Соответствие, где ключом будет выступать код товара (строка), а значением — ссылка на номенклатуру.
  4. Повторно обойдем строки Excel и будем моментально получать ссылки из соответствия в оперативной памяти (без обращений к СУБД).

Посмотрим на пример реализации такого подхода:


&НаСервере
Функция ПолучитьСоответствиеНоменклатурыПоКодам(МассивКодов)
    
    СоответствиеКодов = Новый Соответствие;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура.Ссылка КАК Ссылка,
        |   Номенклатура.Код КАК Код
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   Номенклатура.Код В(&МассивКодов)";
        
    Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        СоответствиеКодов.Вставить(Выборка.Код, Выборка.Ссылка);
    КонецЦикла;
    
    Возврат СоответствиеКодов;
    
КонецФункции

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

Отказ от COM: чтение файлов Excel средствами платформы 1С

Использование технологии COM (`COMОбъект("Excel.Application")`) имеет огромные недостатки в современных реалиях разработки:

Начиная с версии 8.3, платформа 1С предоставляет мощный встроенный инструмент — ТабличныйДокумент, который умеет напрямую считывать файлы форматов .xls и .xlsx без использования внешних библиотек и стороннего ПО. Это решение полностью кроссплатформенно и работает в разы быстрее.

Рассмотрим пример загрузки данных с использованием ТабличногоДокумента:


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

&НаСервере
Процедура ОбработатьТабличныйДокументНаСервере(ТабДок)
    
    КоличествоСтрок = ТабДок.ВысотаТаблицы;
    МассивКодов = Новый Массив;
    ТаблицаДанных = Новый ТаблицаЗначений;
    ТаблицаДанных.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка"));
    ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
    
    // Шаг 1. Собираем данные в таблицу и формируем массив для поиска
    Для НомерСтроки = 2 По КоличествоСтрок Цикл
        
        // Получаем чистые значения из ячеек
        КодТовараЧисло = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки, "ЧГ=0") + "C1").ТекущаяОбласть.Значение;
        Количество = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки, "ЧГ=0") + "C2").ТекущаяОбласть.Значение;
        
        Если ЗначениеЗаполнено(КодТовараЧисло) Тогда
            // Преобразуем код к строке без пробелов
            СтрокаКода = Формат(КодТовараЧисло, "ЧГ=0");
            
            НоваяСтрока = ТаблицаДанных.Добавить();
            НоваяСтрока.Код = СтрокаКода;
            НоваяСтрока.Количество = Число(Количество);
            
            МассивКодов.Добавить(СтрокаКода);
        КонецЕсли;
        
    КонецЦикла;
    
    // Шаг 2. Получаем соответствие номенклатуры одним запросом
    СоответствиеНоменклатуры = ПолучитьСоответствиеНоменклатурыПоКодам(МассивКодов);
    
    // Шаг 3. Заполняем табличную часть документа из таблицы данных
    Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
        
        НайденнаяСсылка = СоответствиеНоменклатуры.Получить(СтрокаДанных.Код);
        
        Если НайденнаяСсылка <> Неопределено Тогда
            НоваяСтрокаДок = Объект.ПрайсЛист.Добавить();
            НоваяСтрокаДок.Номенклатура = НайденнаяСсылка;
            НоваяСтрокаДок.Количество = СтрокаДанных.Количество;
        Иначе
            ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
                НСтр("ru = 'Товар с кодом " + СтрокаДанных.Код + " не найден в справочнике!'")
            );
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

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

← На главную