При разработке обработок загрузки данных из внешних текстовых файлов разработчики часто сталкиваются с двумя типовыми проблемами. Первая — это некорректный контроль уникальности элементов, приводящий либо к постоянному созданию дублей, либо к полной блокировке записи данных. Вторая — необходимость корректного заполнения ссылочных реквизитов, связывающих несколько справочников (например, товара и его производителя), прямо во время чтения файла. Разберем по шагам причины возникающих ошибок и детально изучим надежные способы загрузки данных.
Проанализируем исходную ситуацию. В коде автора производилось чтение файла, разделенного знаками табуляции, и поиск существующей позиции:
Позиция = Найти(Стр, " ");
Спр = Справочники.Товары.СоздатьЭлемент();
Спр.Наименование = Сред(Стр, 1, Позиция - 1);
НайденнаяСтрока = Справочники.Товары.НайтиПоНаименованию(Стр);
В данном фрагменте допущена грубая ошибка: в метод НайтиПоНаименованию передается вся исходная строка Стр, содержащая сразу и товар, и производителя, разделенные табулятором. Разумеется, поиск по такой комбинированной строке никогда не находил совпадения в справочнике, где хранится только чистое наименование товара, что ломало логику условия.
Для исправления достаточно передавать в поиск именно выделенное наименование: Справочники.Товары.НайтиПоНаименованию(Сред(Стр, 1, Позиция - 1)).
Однако здесь скрывается еще одна важная особенность платформы 1С: метод НайтиПоНаименованию принимает второй необязательный параметр — ТочноеСоответствие. По умолчанию он имеет значение Ложь. Это означает, что при попытке найти товар «Болт» платформа может вернуть элемент «Болт М8», так как поиск выполняется по началу строки. Чтобы исключить ложные совпадения при контроле дублей, всегда необходимо передавать значение Истина:
ИмяТовара = СокрЛП(Сред(Стр, 1, Позиция - 1));
НайденныйТовар = Справочники.Товары.НайтиПоНаименованию(ИмяТовара, Истина);
Выясним причину проблем с определением позиций символов. Не следует вставлять фактический знак табуляции непосредственно между кавычками в строковом литерале. При копировании программного кода между окнами конфигуратора или внешними редакторами невидимый символ табуляции может быть автоматически преобразован в обычные пробелы.
Всегда используйте предопределенный специальный символ Символы.Таб. Более того, вместо ручного вычисления позиций через Найти и Сред гораздо удобнее использовать функцию СтрРазделить. Также полезно помнить, что функция СокрЛП удаляет только стандартные пробелы (код 32). Во внешних выгрузках часто встречается неразрывный пробел (код 160), который необходимо заранее заменять:
Стр = СтрЗаменить(Стр, Символы.НПП, " ");
МассивКолонок = СтрРазделить(Стр, Символы.Таб, Ложь);
Если МассивКолонок.Количество() >= 2 Тогда
ИмяТовара = СокрЛП(МассивКолонок[0]);
ИмяПроизводителя = СокрЛП(МассивКолонок[1]);
КонецЕсли;
Рассмотрим решение проблемы, когда реквизит «Производитель» в справочнике «Товары» имеет ссылочный тип СправочникСсылка.Производители. Автору показалось, что заполнить такой реквизит за один проход невозможно. Это заблуждение: создавать элементы и устанавливать связи можно в рамках одного цикла.
Главная сложность заключается в производительности: если в файле тысячи строк, то постоянный поиск производителя и товара прямым обращением к базе данных критически замедлит работу. Чтобы оптимизировать процесс, применим объект Соответствие в качестве кэша в оперативной памяти.
Разберем реализацию этого алгоритма:
ЧтениеТекста для эффективного построчного чтения без загрузки всего файла в память, с обязательным указанием кодировки.КэшПроизводителей, куда сохраняем ссылки найденных или созданных производителей по их именам.ОбменДанными.Загрузка = Истина.
ФайлТекста = Новый ЧтениеТекста("C:\1.txt", КодировкаТекста.UTF8);
Стр = ФайлТекста.ПрочитатьСтроку();
КэшПроизводителей = Новый Соответствие;
Пока Стр <> Неопределено Цикл
Стр = СтрЗаменить(Стр, Символы.НПП, " ");
Колонки = СтрРазделить(Стр, Символы.Таб, Ложь);
Если Колонки.Количество() >= 2 Тогда
ИмяТовара = СокрЛП(Колонки[0]);
ИмяПроизводителя = СокрЛП(Колонки[1]);
Если ЗначениеЗаполнено(ИмяТовара) Тогда
// 1. Получаем ссылку на производителя через кэш
СсылкаПроизводителя = КэшПроизводителей[ИмяПроизводителя];
Если СсылкаПроизводителя = Неопределено И ЗначениеЗаполнено(ИмяПроизводителя) Тогда
СсылкаПроизводителя = Справочники.Производители.НайтиПоНаименованию(ИмяПроизводителя, Истина);
Если СсылкаПроизводителя.Пустая() Тогда
НовыйПроизводитель = Справочники.Производители.СоздатьЭлемент();
НовыйПроизводитель.Наименование = ИмяПроизводителя;
НовыйПроизводитель.ОбменДанными.Загрузка = Истина;
НовыйПроизводитель.Записать();
СсылкаПроизводителя = НовыйПроизводитель.Ссылка;
КонецЕсли;
КэшПроизводителей.Вставить(ИмяПроизводителя, СсылкаПроизводителя);
КонецЕсли;
// 2. Ищем и создаем товар
СсылкаТовара = Справочники.Товары.НайтиПоНаименованию(ИмяТовара, Истина);
Если СсылкаТовара.Пустая() Тогда
НовыйТовар = Справочники.Товары.СоздатьЭлемент();
НовыйТовар.Наименование = ИмяТовара;
НовыйТовар.Производитель = СсылкаПроизводителя;
НовыйТовар.ОбменДанными.Загрузка = Истина;
НовыйТовар.Записать();
КонецЕсли;
КонецЕсли;
КонецЕсли;
Стр = ФайлТекста.ПрочитатьСтроку();
КонецЦикла;
ФайлТекста.Закрыть();
Рассмотрим альтернативный архитектурный подход, предложенный участниками обсуждения. Он заключается в предварительной выгрузке данных в ТаблицаЗначений, очистке дублей и пакетной обработке с помощью запроса.
Разберем логику работы данного метода:
Товар и Производитель (типа Строка).ТЗДанные.Свернуть("Товар, Производитель").Такой подход идеален при работе с большими файлами, так как всю работу по сопоставлению берет на себя СУБД, сводя количество обращений к базе к минимуму.
В процессе обсуждения автор упомянул, что для одного наименования товара в файле может быть указано несколько разных производителей. Разберем важный архитектурный аспект этой проблемы.
Если реквизит «Производитель» находится в шапке справочника «Товары» и имеет тип СправочникСсылка.Производители, он физически может содержать только одно значение. Создавать дубликаты номенклатуры с одинаковыми названиями под каждого производителя — грубая ошибка учета, которая приведет к хаосу в складских остатках и отчетах.
Если по бизнес-логике один товар действительно может поставляться разными производителями, правильными решениями в структуре метаданных будут: