Как загрузить данные из текстового файла в справочники 1С с проверкой уникальности и связыванием реквизитов?

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

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

Анализ ошибки поиска по наименованию при разборе строки

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


Позиция = Найти(Стр, "    ");
Спр = Справочники.Товары.СоздатьЭлемент();
Спр.Наименование = Сред(Стр, 1, Позиция - 1);
НайденнаяСтрока = Справочники.Товары.НайтиПоНаименованию(Стр);

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

Для исправления достаточно передавать в поиск именно выделенное наименование: Справочники.Товары.НайтиПоНаименованию(Сред(Стр, 1, Позиция - 1)).

Однако здесь скрывается еще одна важная особенность платформы 1С: метод НайтиПоНаименованию принимает второй необязательный параметр — ТочноеСоответствие. По умолчанию он имеет значение Ложь. Это означает, что при попытке найти товар «Болт» платформа может вернуть элемент «Болт М8», так как поиск выполняется по началу строки. Чтобы исключить ложные совпадения при контроле дублей, всегда необходимо передавать значение Истина:


ИмяТовара = СокрЛП(Сред(Стр, 1, Позиция - 1));
НайденныйТовар = Справочники.Товары.НайтиПоНаименованию(ИмяТовара, Истина);

Корректная работа с разделителями и пробелами

Выясним причину проблем с определением позиций символов. Не следует вставлять фактический знак табуляции непосредственно между кавычками в строковом литерале. При копировании программного кода между окнами конфигуратора или внешними редакторами невидимый символ табуляции может быть автоматически преобразован в обычные пробелы.

Всегда используйте предопределенный специальный символ Символы.Таб. Более того, вместо ручного вычисления позиций через Найти и Сред гораздо удобнее использовать функцию СтрРазделить. Также полезно помнить, что функция СокрЛП удаляет только стандартные пробелы (код 32). Во внешних выгрузках часто встречается неразрывный пробел (код 160), который необходимо заранее заменять:


Стр = СтрЗаменить(Стр, Символы.НПП, " ");
МассивКолонок = СтрРазделить(Стр, Символы.Таб, Ложь);
Если МассивКолонок.Количество() >= 2 Тогда
    ИмяТовара = СокрЛП(МассивКолонок[0]);
    ИмяПроизводителя = СокрЛП(МассивКолонок[1]);
КонецЕсли;

Способ 1. Загрузка и связывание в один проход с кэшированием

Рассмотрим решение проблемы, когда реквизит «Производитель» в справочнике «Товары» имеет ссылочный тип СправочникСсылка.Производители. Автору показалось, что заполнить такой реквизит за один проход невозможно. Это заблуждение: создавать элементы и устанавливать связи можно в рамках одного цикла.

Главная сложность заключается в производительности: если в файле тысячи строк, то постоянный поиск производителя и товара прямым обращением к базе данных критически замедлит работу. Чтобы оптимизировать процесс, применим объект Соответствие в качестве кэша в оперативной памяти.

Разберем реализацию этого алгоритма:

  1. Используем объект ЧтениеТекста для эффективного построчного чтения без загрузки всего файла в память, с обязательным указанием кодировки.
  2. Создаем коллекцию КэшПроизводителей, куда сохраняем ссылки найденных или созданных производителей по их именам.
  3. При разборе строки проверяем производителя в кэше, и только при его отсутствии обращаемся к базе или создаем нового.
  4. Ищем товар с точным соответствием, создаем его при отсутствии, привязываем ссылку на производителя и записываем.
  5. Для максимальной скорости отключаем тяжелые прикладные обработчики свойством ОбменДанными.Загрузка = Истина.

ФайлТекста = Новый ЧтениеТекста("C:\1.txt", КодировкаТекста.UTF8);
Стр = ФайлТекста.ПрочитатьСтроку();

КэшПроизводителей = Новый Соответствие;

Пока Стр <> Неопределено Цикл
    Стр = СтрЗаменить(Стр, Символы.НПП, " ");
    Колонки = СтрРазделить(Стр, Символы.Таб, Ложь);
    
    Если Колонки.Количество() >= 2 Тогда
        ИмяТовара = СокрЛП(Колонки[0]);
        ИмяПроизводителя = СокрЛП(Колонки[1]);
        
        Если ЗначениеЗаполнено(ИмяТовара) Тогда
            // 1. Получаем ссылку на производителя через кэш
            СсылкаПроизводителя = КэшПроизводителей[ИмяПроизводителя];
            Если СсылкаПроизводителя = Неопределено И ЗначениеЗаполнено(ИмяПроизводителя) Тогда
                СсылкаПроизводителя = Справочники.Производители.НайтиПоНаименованию(ИмяПроизводителя, Истина);
                
                Если СсылкаПроизводителя.Пустая() Тогда
                    НовыйПроизводитель = Справочники.Производители.СоздатьЭлемент();
                    НовыйПроизводитель.Наименование = ИмяПроизводителя;
                    НовыйПроизводитель.ОбменДанными.Загрузка = Истина;
                    НовыйПроизводитель.Записать();
                    СсылкаПроизводителя = НовыйПроизводитель.Ссылка;
                КонецЕсли;
                
                КэшПроизводителей.Вставить(ИмяПроизводителя, СсылкаПроизводителя);
            КонецЕсли;
            
            // 2. Ищем и создаем товар
            СсылкаТовара = Справочники.Товары.НайтиПоНаименованию(ИмяТовара, Истина);
            Если СсылкаТовара.Пустая() Тогда
                НовыйТовар = Справочники.Товары.СоздатьЭлемент();
                НовыйТовар.Наименование = ИмяТовара;
                НовыйТовар.Производитель = СсылкаПроизводителя;
                НовыйТовар.ОбменДанными.Загрузка = Истина;
                НовыйТовар.Записать();
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
    
    Стр = ФайлТекста.ПрочитатьСтроку();
КонецЦикла;

ФайлТекста.Закрыть();

Способ 2. Загрузка через предварительную обработку в Таблице Значений

Рассмотрим альтернативный архитектурный подход, предложенный участниками обсуждения. Он заключается в предварительной выгрузке данных в ТаблицаЗначений, очистке дублей и пакетной обработке с помощью запроса.

Разберем логику работы данного метода:

  1. Считываем текстовый документ и наполняем таблицу значений с колонками Товар и Производитель (типа Строка).
  2. Сворачиваем таблицу значений, чтобы исключить абсолютно идентичные строки еще до обращения к базе данных: ТЗДанные.Свернуть("Товар, Производитель").
  3. Передаем таблицу значений как параметр во временную таблицу запроса.
  4. В запросе связываем данные внешнего файла со справочниками по полям наименований через левое соединение.
  5. В результате выборки получаем готовый перечень только тех позиций, которые действительно отсутствуют в информационной базе, и создаем их без холостых проверок.

Такой подход идеален при работе с большими файлами, так как всю работу по сопоставлению берет на себя СУБД, сводя количество обращений к базе к минимуму.

Ограничение: связь одного товара с несколькими производителями

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

Если реквизит «Производитель» находится в шапке справочника «Товары» и имеет тип СправочникСсылка.Производители, он физически может содержать только одно значение. Создавать дубликаты номенклатуры с одинаковыми названиями под каждого производителя — грубая ошибка учета, которая приведет к хаосу в складских остатках и отчетах.

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

  1. Перенос производителей в табличную часть справочника «Товары».
  2. Использование независимого регистра сведений (например, «ПроизводителиНоменклатуры») с двумя измерениями: «Товар» и «Производитель».
  3. Ведение учета в разрезе характеристик номенклатуры, где конкретный производитель будет являться свойством характеристики.
← На главную