При импорте больших объемов информации в базу данных 1С с помощью стандартной обработки «Загрузка данных из табличного документа» специалисты часто сталкиваются с проблемой дублирования наименований. Если в целевом справочнике включен контроль уникальности только внутри группы (папки), то элементы с одинаковыми названиями могут вполне легально существовать в разных ветках иерархии. Например, элемент «Частный сектор» может одновременно находиться в родительской группе «Водопотребление физических лиц» и в группе «Водопотребление юридических лиц».
При попытке сопоставить данные по наименованию стандартный механизм поиска выдает критическую ошибку: Неоднозначное представление. Вариантов: 2. Система не понимает, какую именно ссылку нужно подставить в загружаемый документ или справочник, и останавливает процесс. Разберем по шагам, как решить эту проблему с помощью встроенного языка 1С, используя режим вычисления выражений.
Для обхода стандартного плоского поиска мы можем задействовать мощный инструмент обработки — флажок «Вычислять» в настройках загрузки поля. Когда этот флаг установлен, система перестает искать элемент стандартным запросом по наименованию. Вместо этого она выполняет произвольный код на встроенном языке 1С, написанный нами в поле «Выражение».
Этот код выполняется на стороне сервера, что открывает перед нами неограниченные возможности: мы можем использовать запросы, обращаться к методам менеджеров справочников, использовать объектные API и даже вызывать экспортные функции общих модулей конфигурации.
При написании кода в поле «Выражение» нам доступен ряд предопределенных переменных:
Результат — главная переменная, в которую мы обязаны записать конечную ссылку на найденный или созданный элемент справочника.ТекстЯчейки — строковое значение, считанное из текущей ячейки файла Excel (табличного документа).ТекстыЯчеек — массив строк всей текущей строки файла, позволяющий обращаться к соседним колонкам (например, ТекстыЯчеек[0]).ТекущиеДанные — структура, содержащая уже обработанные и сопоставленные значения для текущей строки загрузки.ОписаниеОшибки — текстовое поле, куда мы можем записать понятную причину сбоя, если сопоставить элемент не удалось. Этот текст будет выведен в лог или примечание к строке.Рассмотрим самый простой и быстрый способ решения задачи, не требующий написания тяжелых SQL-подобных запросов. Системный метод платформы НайтиПоНаименованию имеет необязательные параметры, которые позволяют жестко задать родительскую группу для поиска.
Проанализируем следующий алгоритм: сначала мы находим саму родительскую группу (например, «Частный сектор»), а затем выполняем поиск импортируемого элемента только внутри этой группы.
Вставим в поле «Выражение» следующий программный код:
// Шаг 1. Находим родительскую группу, внутри которой нужно искать элементы
РодительскаяГруппа = Справочники.КатегорииВодопотребления.НайтиПоНаименованию("Частный сектор", Истина);
// Шаг 2. Выполняем поиск элемента строго внутри этой группы
Если ЗначениеЗаполнено(РодительскаяГруппа) Тогда
// Третий параметр метода НайтиПоНаименованию отвечает за родительскую ветку справочника
Результат = Справочники.КатегорииВодопотребления.НайтиПоНаименованию(ТекстЯчейки, Истина, РодительскаяГруппа);
Иначе
Результат = Справочники.КатегорииВодопотребления.ПустаяСсылка();
ОписаниеОшибки = "Критическая ошибка: в базе данных не найдена родительская папка 'Частный сектор'!";
КонецЕсли;
Этот способ прекрасно подходит для простых иерархий, когда все искомые элементы лежат непосредственно на первом уровне вложенности внутри указанной папки. Метод работает быстро за счет платформенного кэширования стандартных выборок.
Выясним, что делать в более сложных ситуациях. Например, когда справочник имеет многоуровневую структуру, и искомые элементы могут находиться не только в самой папке «Частный сектор», но и в ее дочерних подпапках. В этом случае стандартный метод НайтиПоНаименованию с указанием родителя не сработает для глубоких уровней вложенности.
Для решения этой задачи мы напишем полноценный запрос с использованием конструкции В ИЕРАРХИИ. Напишем код для поля «Выражение»:
// Определяем корневую родительскую группу для ограничения области поиска
РодительскаяГруппа = Справочники.КатегорииВодопотребления.НайтиПоНаименованию("Частный сектор", Истина);
Если ЗначениеЗаполнено(РодительскаяГруппа) Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Категории.Ссылка КАК Ссылка
|ИЗ
| Справочник.КатегорииВодопотребления КАК Категории
|ГДЕ
| Категории.Наименование = &Наименование
| И Категории.Ссылка В ИЕРАРХИИ(&РодительскаяГруппа)
| И НЕ Категории.ЭтоГруппа"; // Исключаем из результатов поиска папки
Запрос.УстановитьПараметр("Наименование", СокрЛП(ТекстЯчейки));
Запрос.УстановитьПараметр("РодительскаяГруппа", РодительскаяГруппа);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Результат = Выборка.Ссылка;
Иначе
Результат = Справочники.КатегорииВодопотребления.ПустаяСсылка();
КонецЕсли;
Иначе
Результат = Справочники.КатегорииВодопотребления.ПустаяСсылка();
ОписаниеОшибки = "Не найдена группа-родитель для поиска!";
КонецЕсли;
Этот код гарантирует, что система найдет нужную запись, даже если она скрыта глубоко внутри структуры папки «Частный сектор», и полностью проигнорирует аналогичные элементы из папки «Город».
При использовании стандартного поиска обработка загрузки умеет автоматически создавать новые элементы, если они не найдены в базе. Однако, как только мы переходим в режим «Вычислять», эта обязанность полностью ложится на наши плечи.
Если в процессе импорта мы хотим не только искать существующие элементы, но и автоматически создавать новые в случае их отсутствия, нам необходимо расширить наш код блоком автосоздания объекта. Посмотрим, как это реализовать:
РодительскаяГруппа = Справочники.КатегорииВодопотребления.НайтиПоНаименованию("Частный сектор", Истина);
Если ЗначениеЗаполнено(РодительскаяГруппа) Тогда
// Ищем существующий элемент
НайденнаяСсылка = Справочники.КатегорииВодопотребления.НайтиПоНаименованию(ТекстЯчейки, Истина, РодительскаяГруппа);
Если ЗначениеЗаполнено(НайденнаяСсылка) Тогда
Результат = НайденнаяСсылка;
Иначе
// Создаем новый элемент, если поиск не дал результатов
НовыйОбъект = Справочники.КатегорииВодопотребления.СоздатьЭлемент();
НовыйОбъект.Наименование = СокрЛП(ТекстЯчейки);
НовыйОбъект.Родитель = РодительскаяГруппа;
// Здесь можно заполнить другие обязательные реквизиты по умолчанию
// НовыйОбъект.СтавкаНДС = Справочники.СтавкиНДС.БезНДС;
Попытка
НовыйОбъект.Записать();
Результат = НовыйОбъект.Ссылка;
Исключение
Результат = Справочники.КатегорииВодопотребления.ПустаяСсылка();
ОписаниеОшибки = "Не удалось записать новый элемент справочника: " + ОписаниеОшибки();
КонецПопытки;
EndЕсли;
Иначе
Результат = Справочники.КатегорииВодопотребления.ПустаяСсылка();
ОписаниеОшибки = "Родительская папка не найдена, создание элементов невозможно!";
КонецЕсли;
Использование конструкции Попытка...Исключение при записи нового объекта крайне важно. Это защитит процесс импорта от аварийного завершения, если при записи элемента сработают подписки на события или жесткие бизнес-локи конфигурации.