Как избавиться от запросов в цикле при получении счетов учета номенклатуры в Конвертации данных

Программист 1С v8.3 (Обычные формы) 1С:ERP Управление предприятием Бухгалтерский учет
← На главную

Перенос данных из одной системы в другую (например, из УПП 1.3 в ERP 2.5) часто сопряжен с необходимостью вычисления параметров «на лету». Типичная ситуация: в источнике (УПП) счета учета номенклатуры хранятся в регистре сведений СчетаУчетаНоменклатуры. При этом настройки могут быть заданы как для конкретной номенклатуры, так и для её родителя или родителя родителя (до 6 уровней вложенности). Попытка получить эти данные через прямой запрос внутри обработчика ПКО (Правила конвертации объектов) приводит к катастрофическому падению производительности из-за «запросов в цикле».

В этой статье мы подробно разберем, как исправить «грех» запросов в цикле и сделать миграцию данных быстрой и профессиональной. Мы рассмотрим несколько подходов: от микро-оптимизации до использования кэширования и типовых механизмов конфигурации.

Вариант 1. Минимальная оптимизация текущего кода

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

Проанализируем пример кода, который позволит немного ускорить процесс без кардинальной переработки алгоритма:


// Создаем объект один раз перед началом поиска
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   СчетаУчетаНоменклатуры.СчетУчетаБУ КАК СЧ,
    |   СчетаУчетаНоменклатуры.СчетДоходовОтРеализацииБУ КАК СЧД
    |ИЗ
    |   РегистрСведений.СчетаУчетаНоменклатуры КАК СчетаУчетаНоменклатуры
    |ГДЕ
    |   СчетаУчетаНоменклатуры.Номенклатура = &Номенклатура";

// Основной цикл поиска по иерархии
ЭлОтбора = Источник.Ссылка;
Счет = Неопределено;
СчИтераций = 1;

Пока ЭлОтбора <> Справочники.Номенклатура.ПустаяСсылка() И СчИтераций <= 7 Цикл
    Запрос.УстановитьПараметр("Номенклатура", ЭлОтбора);
    Результат = Запрос.Выполнить();
    Выборка = Результат.Выбрать();
    
    Если Выборка.Следующий() Тогда
        Счет = Выборка.СЧ;
        Если ЗначениеЗаполнено(Счет) Тогда
            Прервать; // Счет найден, выходим из цикла
        КонецЕсли;
    КонецЕсли;
    
    // Переходим к родителю для следующей попытки
    ЭлОтбора = ЭлОтбора.Родитель;
    СчИтераций = СчИтераций + 1;
КонецЦикла;

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

Вариант 2. Использование кэширования через Таблицу Значений или Соответствие

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

Шаг 1. Подготовка кэша (Обработчик «Перед выгрузкой данных» в ПВД или Глобальный обработчик)

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   СчетаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
    |   СчетаУчетаНоменклатуры.СчетУчетаБУ КАК Счет
    |ИЗ
    |   РегистрСведений.СчетаУчетаНоменклатуры КАК СчетаУчетаНоменклатуры";

ТЗ_Счета = Запрос.Выполнить().Выгрузить();
ТЗ_Счета.Индексировать("Номенклатура");

// Помещаем таблицу в параметры конвертации, чтобы она была доступна везде
Параметры.Вставить("КэшСчетовНоменклатуры", ТЗ_Счета);

Шаг 2. Использование кэша в ПКО (Событие «Перед выгрузкой» для реквизита)

Теперь вместо Запрос.Выполнить() мы используем поиск по индексированной таблице значений. Это работает в десятки раз быстрее.


Кэш = Параметры.КэшСчетовНоменклатуры;
ТекущийОбъект = Источник.Ссылка;
НайденныйСчет = Неопределено;

Для Итерация = 1 По 7 Цикл
    Если ТекущийОбъект.Пустая() Тогда Прервать; КонецЕсли;
    
    // Ищем строку в кэше
    СтрокаКэша = Кэш.Найти(ТекущийОбъект, "Номенклатура");
    Если СтрокаКэша <> Неопределено И ЗначениеЗаполнено(СтрокаКэша.Счет) Тогда
        НайденныйСчет = СтрокаКэша.Счет;
        Прервать;
    КонецЕсли;
    
    ТекущийОбъект = ТекущийОбъект.Родитель;
КонецЦикла;

Значение = НайденныйСчет;

Для еще большего ускорения вместо ТаблицыЗначений можно использовать объект Соответствие (Map), так как поиск в нем по ключу (ссылке) является максимально быстрым.

Вариант 3. Иерархический запрос с левыми соединениями

Если мы заранее знаем, что глубина иерархии ограничена (в нашем случае 6-7 уровней), мы можем получить итоговый счет за один запрос для каждого элемента, не используя циклы 1С вовсе. Мы «развернем» иерархию в одну строку с помощью левых соединений таблицы номенклатуры самой с собой.

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


ВЫБРАТЬ
    ЕСТЬNULL(СчетаУр0.СчетУчетаБУ, 
        ЕСТЬNULL(СчетаУр1.СчетУчетаБУ, 
            ЕСТЬNULL(СчетаУр2.СчетУчетаБУ, 
                ЕСТЬNULL(СчетаУр3.СчетУчетаБУ, "Не определено")))) КАК ИтоговыйСчет
ИЗ
    Справочник.Номенклатура КАК Н_Элемент
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СчетаУчетаНоменклатуры КАК СчетаУр0 
        ПО Н_Элемент.Ссылка = СчетаУр0.Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СчетаУчетаНоменклатуры КАК СчетаУр1 
        ПО Н_Элемент.Родитель = СчетаУр1.Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СчетаУчетаНоменклатуры КАК СчетаУр2 
        ПО Н_Элемент.Родитель.Родитель = СчетаУр2.Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СчетаУчетаНоменклатуры КАК СчетаУр3 
        ПО Н_Элемент.Родитель.Родитель.Родитель = СчетаУр3.Номенклатура
ГДЕ
    Н_Элемент.Ссылка = &Ссылка

Функция ЕСТЬNULL (ISNULL) здесь работает как фильтр приоритетов: если на уровне самого элемента счета нет, берется значение родителя, и так далее до самого верха. Это решение позволяет серверу базы данных выполнить всю работу за один проход.

Вариант 4. Использование типовых функций конфигурации УПП

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

Рассмотрим пример вызова такой функции:


// В УПП часто используется подобный механизм
МассивНоменклатуры = Новый Массив;
МассивНоменклатуры.Добавить(Источник.Ссылка);

// Вызов типовой функции, которая умеет искать счета с учетом иерархии
ТаблицаСчетов = УправлениеЗапасами.ПолучитьСчетаУчетаНоменклатуры(МассивНоменклатуры, Организация);

Если ТаблицаСчетов.Количество() > 0 Тогда
    Счет = ТаблицаСчетов[0].СчетУчетаБУ;
КонецЕсли;

Преимущество: типовые функции обычно максимально оптимизированы (используют временные таблицы и пакетные запросы).

Недостаток: если вы делаете выгрузку на стороне ERP (подключаясь к УПП через COM), вызов серверных функций может быть медленным. В этом случае лучше использовать Вариант 2.

Вариант 5. Предварительная обработка данных в источнике

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

Тогда в правилах конвертации (ПКО) вам не нужно будет писать никакого кода: реквизит СчетУчета_ДляВыгрузки просто сопоставится с нужным полем в ERP. Это самый стабильный и быстрый метод для «тяжелых» миграций.

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

Подведем итоги нашего анализа:

  1. Для небольших баз: достаточно вынести объект Запрос за цикл и добавить Прервать.
  2. Для средних и больших баз: используйте кэширование в Параметры.КэшСчетовНоменклатуры через Таблицу Значений или Соответствие. Это самый универсальный метод в КД 2.0.
  3. Для сложных иерархий: если уровней больше 6, лучше использовать типовую функцию УправлениеЗапасами.ПолучитьСчетаУчетаНоменклатуры, так как она корректно обрабатывает все нюансы (склады, организации).
  4. При переносе в ERP: помните, что в ERP 2.5 счета учета определяются через Группы финансового учета (ГФУ). Ваша задача в КД — не просто перенести номер счета, а сопоставить его с соответствующим элементом справочника ГруппыФинансовогоУчетаНоменклатуры.

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

← На главную