Как оптимизировать медленный запрос и код при распределении дополнительных расходов по ГТД в 1С?

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

При работе с документами импорта (ГТД и поступление дополнительных расходов) часто возникают задачи по распределению затрат на себестоимость товаров. Если данных много, стандартные механизмы или неоптимально написанные запросы могут выполняться крайне медленно. Рассмотрим ситуацию, когда выполнение кода занимает более 90 секунд, и разберем пошагово, как ускорить этот процесс, оптимизировав структуру запроса и алгоритм обработки в коде 1С.

Проанализируем основные проблемы производительности

В представленном исходном коде можно выделить несколько критических «узких мест», которые в совокупности дают значительную задержку:

  1. Неявные соединения (обращения «через точку»): Использование конструкций вида ГТДИмпортТовары.Ссылка.ДоговорВзаиморасчетов.ВалютаВзаиморасчетов заставляет SQL-сервер создавать каскад левых соединений. Чем больше таких точек, тем сложнее план запроса.
  2. Множественное чтение одной и той же таблицы: Конструкция ОБЪЕДИНИТЬ ВСЕ трижды обращается к табличной части Документ.ГТДИмпорт.Товары для получения пошлины, НДС и сборов по отдельности.
  3. Вложенные запросы и соединения с ними: Соединение основной выборки с вложенным запросом по заказу поставщика лишает оптимизатор SQL возможности эффективно использовать индексы.
  4. Запросы в цикле: Вызов функции _обПересчет внутри цикла обхода выборки. Если функция обращается к регистру сведений КурсыВалют, это порождает сотни лишних запросов к базе данных.
  5. Линейный поиск в таблице значений: Метод Найти() в таблице значений ТЗ_Товары работает медленно на больших объемах данных, если для таблицы не построены индексы.

Шаг 1: Оптимизация структуры запроса и использование временных таблиц

Первым делом избавимся от вложенных запросов и обращений через точку. Для этого перенесем предварительные данные во временные таблицы (ВТ) и добавим необходимые индексы. Это позволит системе подготовить данные один раз и быстро обращаться к ним при итоговом соединении.

Рассмотрим пример подготовки данных по номенклатуре из документа-основания:


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

Обратите внимание: мы используем конструкцию ИНДЕКСИРОВАТЬ ПО. Это критически важно для производительности при последующих соединениях этих таблиц.

Шаг 2: Устранение лишних объединений (UNION ALL)

Вместо того чтобы трижды сканировать таблицу товаров ГТД для получения разных сумм, мы можем выбрать их в один проход, а затем «развернуть» данные. Однако в 1С часто проще и быстрее выбрать суммы в разные колонки, а затем просуммировать их или разделить логикой ВЫБОР КОГДА.

Проанализируем пример более эффективного получения данных по ГТД:


"ВЫБРАТЬ
|   ГТДТовары.Номенклатура КАК Номенклатура,
|   СУММА(ГТДТовары.СуммаПошлины) КАК СуммаПошлины,
|   СУММА(ГТДТовары.СуммаНДС) КАК СуммаНДС,
|   СУММА(ГТДТовары.СуммаТаможня) КАК СуммаТаможня,
|   ДанныеГТД.Контрагент,
|   ДанныеГТД.Договор,
|   ДанныеГТД.Валюта,
|   ДанныеГТД.Курс
|ИЗ
|   Документ.ГТДИмпорт.Товары КАК ГТДТовары
|   ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДанныеГТД КАК ДанныеГТД
|   ПО ГТДТовары.Ссылка = ДанныеГТД.Ссылка
|   ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Номенклатура КАК ВТ_Ном
|   ПО ГТДТовары.Номенклатура = ВТ_Ном.Номенклатура
|СГРУППИРОВАТЬ ПО
|   ГТДТовары.Номенклатура,
|   ДанныеГТД.Контрагент,
|   ДанныеГТД.Договор,
|   ДанныеГТД.Валюта,
|   ДанныеГТД.Курс";

Шаг 3: Перенос расчетов валют в запрос

Чтобы избежать вызова функции _обПересчет в цикле, необходимо получить курсы валют прямо в запросе. Мы можем соединить итоговую таблицу с виртуальной таблицей РегистрСведений.КурсыВалют.СрезПоследних.

Выясним причину замедления: если в документе 500 строк номенклатуры, и для каждой вызывается функция пересчета, 1С выполняет 500 маленьких запросов к базе. Соединение в одном большом запросе выполняется один раз на стороне сервера БД, что в десятки раз быстрее.

Шаг 4: Оптимизация работы с Таблицей Значений в коде

Если часть логики все же остается в коде (например, расчет пропорций), необходимо ускорить поиск по вспомогательным таблицам. В исходном коде используется ТЗ_Товары.Найти(Выборка.Номенклатура, "Номенклатура").

Выполним индексацию таблицы значений перед началом цикла:


ТЗ_Товары.Индексы.Добавить("Номенклатура");
// Теперь метод Найти() будет работать практически мгновенно
Пока Выборка.Следующий() Цикл
    нс = ТЗ_Товары.Найти(Выборка.Номенклатура, "Номенклатура");
    // Дальнейшая логика
КонецЦикла;

Итоговые рекомендации

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

Применив эти подходы, время выполнения задачи можно сократить с 90 секунд до 1-2 секунд, что значительно повысит комфорт работы пользователей с системой.

← На главную