Как в 1С 7.7 загрузить табличную часть из Excel и исключить поиск номенклатуры в определенной группе (папке)?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При автоматизации импорта данных из внешних источников, таких как прайс-листы поставщиков или накладные в формате Excel, разработчики часто сталкиваются с проблемой неуникальности ключевых полей. Типичный сценарий на платформе «1С:Предприятие 7.7»: поиск номенклатуры выполняется по текстовому полю Артикул. Однако в справочнике Номенклатура помимо рабочей папки может существовать архивная папка, группа для удаленного филиала или папка с устаревшими позициями, в которых артикулы дублируются.

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

В чем заключается проблема стандартного поиска и простых запросов?

Рассмотрим ситуацию, когда разработчик пытается решить задачу «в лоб» через встроенный язык 1С 7.7. Первая мысль, которая приходит — написать запрос к справочнику с условием исключения папки. На форумах часто предлагают конструкцию вида:

|Условие(Родитель <> ГруппаИсключения);

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

Кроме того, выполнение отдельного запроса к базе данных на каждую строчку загружаемого файла Excel крайне неэффективно и вызовет колоссальные задержки при обработке больших файлов.

---

Решение №1: Использование иерархического условия в стандартном запросе

Если мы хотим решить задачу с помощью штатного механизма запросов 1С 7.7, нам необходимо заставить платформу проверять всю ветку иерархии сверху вниз. Для этого в языке запросов существует оператор В. Чтобы исключить группу со всеми её подгруппами любой глубины вложенности, мы должны применить отрицание этого оператора.

Давайте посмотрим на пример построения правильного запроса:


ТекстЗапроса = 
"//{{ЗАПРОС(ПоискНоменклатуры)
|Обрабатывать НеПомеченныеНаУдаление;
|Без итогов;
|ТекущийЭлемент = Справочник.Номенклатура.ТекущийЭлемент;
|Артикул        = Справочник.Номенклатура.Артикул;
|Группировка ТекущийЭлемент без Групп;
|Условие(СокрП(Артикул) = СокрП(ИскомыйАртикул));
|Условие(НЕ(ТекущийЭлемент В ГруппаИсключения));
|"//}}ЗАПРОС
;

Разберем ключевые особенности этого запроса:

  1. Конструкция Группировка ТекущийЭлемент без Групп гарантирует, что в выборку попадут только конечные элементы (товары), а сами папки будут отсечены.
  2. Использование функции СокрП() в условии сравнения строк защищает нас от проблем с концевыми пробелами в артикулах, которые часто встречаются при чтении ячеек из файлов Excel.
  3. Оператор НЕ(ТекущийЭлемент В ГруппаИсключения) работает по всей глубине иерархии. Платформа самостоятельно определит, принадлежит ли элемент этой ветке, независимо от уровня вложенности подпапок.

---

Решение №2: Быстрый штатный поиск через ВыбратьЭлементыПоРеквизиту и ПринадлежитГруппе

Выполнение запросов в цикле для каждой строки импорта — это медленно. Но в 1С 7.7 есть отличный способ быстрого позиционирования по индексированному полю с последующей фильтрацией. Для этого у справочников есть метод ВыбратьЭлементыПоРеквизиту. Этот метод работает мгновенно при условии, что у реквизита Артикул в конфигураторе установлен признак «Сортировка».

Давайте разберем алгоритм совместной работы быстрого поиска и проверки на вхождение в группу:


Спр = СоздатьОбъект("Справочник.Номенклатура");
ИскомыйАртикул = СокрП(ТекущаяСтрокаExcel.Артикул);

// Открываем выборку по значению индексированного реквизита
// Параметры: ИмяРеквизита, Значение, РежимИерархии (0 - без учета), РежимГрупп (0 - только элементы)
Если Спр.ВыбратьЭлементыПоРеквизиту("Артикул", ИскомыйАртикул, 0, 0) = 1 Тогда
    НайденныйТовар = ПолучитьРазрешенныйТовар(Спр, ГруппаИсключения);
    Если ПустоеЗначение(НайденныйТовар) = 0 Тогда
        // Заполняем табличную часть документа
        НоваяСтрока();
        Номенклатура = НайденныйТовар;
        // ... заполнение остальных реквизитов
    Иначе
        Сообщить("Артикул " + ИскомыйАртикул + " найден только в исключаемой группе!", "!");
    EndIf;
КонецЕсли;

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


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

Преимущества этого подхода:

---

Решение №3: Предварительное кэширование в Таблицу Значений

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

Рассмотрим, как реализовать этот подход:


// Создаем Таблицу Значений для хранения разрешенного кэша номенклатуры
ТЗКэш = СоздатьОбъект("ТаблицаЗначений");
ТЗКэш.НоваяКолонка("Артикул", "Строка");
ТЗКэш.НоваяКолонка("Товар", "Справочник.Номенклатура");

// Заполняем кэш только разрешенными элементами
Спр = СоздатьОбъект("Справочник.Номенклатура");
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
    Если Спр.ЭтоГруппа() = 1 Тогда
        Продолжить;
    КонецЕсли;
    
    // Проверяем принадлежность к запрещенной группе
    Если Спр.ПринадлежитГруппе(ГруппаИсключения) = 1 Тогда
        Продолжить;
    КонецЕсли;
    
    // Добавляем разрешенный товар в кэш-таблицу
    ТЗКэш.НоваяСтрока();
    ТЗКэш.Артикул = СокрП(Спр.Артикул);
    ТЗКэш.Товар = Спр.ТекущийЭлемент();
КонецЦикла;

// Для ускорения поиска свернем таблицу или подготовим её к поиску
// Поиск по обычной ТЗ в 1С 7.7 выполняется линейным перебором через НайтиЗначение()

При чтении файла Excel поиск в кэше будет выглядеть следующим образом:


СтрокаНом = 0;
Если ТЗКэш.НайтиЗначение(ИскомыйАртикул, СтрокаНом, "Артикул") = 1 Тогда
    НайденныйТовар = ТЗКэш.ПолучитьЗначение(СтрокаНом, "Товар");
    // Переносим в табличную часть документа...
КонецЕсли;

Важное предупреждение: Метод с обычной ТаблицейЗначений эффективен только на относительно небольших справочниках (до 10 000–15 000 элементов). Если справочник Номенклатура содержит сотни тысяч позиций, заполнение кэша займет много времени, а поиск методом НайтиЗначение будет работать медленно из-за линейной сложности алгоритма поиска O(N).

---

Решение №4: Оптимизация для больших баз с использованием внешней компоненты «1С++»

Для крупных информационных баз со справочниками в сотни тысяч позиций стандартные механизмы 1С 7.7 могут оказаться слишком медленными. Профессиональные разработчики используют для решения таких задач внешнюю компоненту 1С++. Она предоставляет нам два мощных инструмента для ускорения поиска.

Вариант А: Прямой SQL-запрос к базе данных

Если ваша база работает под управлением MS SQL Server (или PostgreSQL в альтернативных сборках), мы можем обойти объектную модель платформы и обратиться к СУБД напрямую. Прямой запрос выполнится за доли секунды.

Пример прямого запроса с использованием макроподстановщика иерархии 1С++:


ODBCЗапрос = СоздатьОбъект("ODBCRecordset");
ТекстSQL = "
|SELECT 
|    id as [Товар $Справочник.Номенклатура]
|FROM 
|    $Справочник.Номенклатура (NOLOCK)
|WHERE 
|    $Справочник.Номенклатура.Артикул = :ВыбАртикул
|    AND isfolder = 2 -- Исключаем группы
|    AND id NOT IN (
|        SELECT id FROM $Справочник.Номенклатура 
|        WHERE parentid = :ГруппаИсключения OR id = :ГруппаИсключения
|    )
|";
ODBCЗапрос.УстановитьТекстовыйПараметр("ВыбАртикул", ИскомыйАртикул);
ODBCЗапрос.УстановитьТекстовыйПараметр("ГруппаИсключения", ГруппаИсключения);
ТабРезилт = ODBCЗапрос.ВыполнитьИнструкцию(ТекстSQL);

Вариант Б: Использование объекта «ИндексированнаяТаблица»

Если мы хотим использовать кэширование в оперативной памяти (как в Решении №3), но объем справочника слишком велик, стандартную ТаблицуЗначений следует заменить на объект ИндексированнаяТаблица из состава компоненты 1С++. Это позволит искать элементы с мгновенной скоростью O(1).


// Инициализируем индексированную таблицу из 1С++
ИндексоваяТаблица = СоздатьОбъект("ИндексированнаяТаблица");
ИндексоваяТаблица.НоваяКолонка("Артикул", "Строка");
ИндексоваяТаблица.НоваяКолонка("Товар", "Справочник.Номенклатура");

// Заполняем таблицу данными (аналогично решению №3)
// ...

// Строим хэш-индекс по колонке "Артикул" для мгновенного поиска
ИндексоваяТаблица.ДобавитьИндекс("ИндексПоАртикулу", "Артикул");

// Поиск в цикле загрузки будет происходить со скоростью обращения к хэш-карте:
Если ИндексоваяТаблица.НайтиКонтекст(ИскомыйАртикул, "ИндексПоАртикулу") = 1 Тогда
    НайденныйТовар = ИндексоваяТаблица.Товар;
    // ...
КонецЕсли;

---

Резюме и рекомендации

Подведем итоги нашего анализа и выберем оптимальный путь решения проблемы:

← На главную