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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Одной из самых распространенных ошибок при разработке на платформе 1С, которая приводит к значительному падению производительности, является выполнение запросов внутри цикла. Эту ситуацию мы часто наблюдаем, когда необходимо получить дополнительные данные (например, цены, остатки или реквизиты) для каждой строки табличной части или выборки документов.

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

В чем заключается проблема запросов в цикле?

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

  1. Запускаем цикл по строкам таблицы.
  2. На каждой итерации (шаге) цикла обращаемся к базе данных, чтобы получить цену товара.
  3. Записываем результат.

Это означает, что платформа 1С совершит 1000 обращений к серверу СУБД (SQL). Каждое обращение требует времени на передачу данных по сети, компиляцию запроса и его выполнение. Даже если один запрос выполняется 0.01 секунды, тысяча запросов займет ощутимое время, блокируя работу пользователя.

Рассмотрим, как выглядит этот неправильный код, чтобы знать врага в лицо:


// НЕПРАВИЛЬНЫЙ ПОДХОД (Запрос в цикле)
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
        |ИЗ
        |   РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура = &Номенклатура) КАК ЦеныНоменклатурыСрезПоследних";
        
    Запрос.УстановитьПараметр("Период", ТекущаяДата());
    Запрос.УстановитьПараметр("Номенклатура", СтрокаТЧ.Номенклатура);
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        СтрокаТЧ.Цена = Выборка.Цена;
    КонецЕсли;
    
КонецЦикла;

Теперь перейдем к правильным решениям. Основная идея оптимизации — получить все необходимые данные одним единственным запросом до или во время обработки данных, а затем распределить их по строкам.

Решение 1: Использование отбора по массиву (Оператор "В")

Если исходных данных немного, можно собрать ссылки на интересующие нас объекты (например, Номенклатуру) в массив и передать его в запрос.

Разберем этот метод по шагам:

  1. Выгружаем колонку "Номенклатура" из табличной части в массив.
  2. Передаем этот массив в параметр запроса.
  3. В условии запроса используем конструкцию ГДЕ Номенклатура В (&МассивНоменклатуры).
  4. Выгружаем результат запроса в таблицу значений или соответствие для быстрого поиска.
  5. Обходим исходную табличную часть и подставляем данные.

Однако этот метод имеет ограничения. Если в массиве будут тысячи элементов, сам текст SQL-запроса станет слишком большим, что тоже может замедлить работу. Поэтому для обработки табличных частей документов наиболее эффективным является второй способ.

Решение 2: Использование Менеджера Временных Таблиц (Рекомендуемый способ)

Этот способ является стандартом профессиональной разработки в 1С. Мы помещаем исходную таблицу (из 1С) во временную таблицу (в СУБД), а затем одним запросом соединяем её с данными регистра (например, с ценами).

Давайте посмотрим, как реализовать этот алгоритм. Нам понадобятся объекты МенеджерВременныхТаблиц и Запрос.

Этап 1: Подготовка данных

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


// Создаем запрос и менеджер
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Передаем табличную часть как параметр
// Важно: Если мы работаем с ТЧ документа на форме, лучше сначала выгрузить её в ТаблицуЗначений
ТЗ_Товары = Объект.Товары.Выгрузить();
Запрос.УстановитьПараметр("ТЗ_Товары", ТЗ_Товары);
Запрос.УстановитьПараметр("Период", ТекущаяДата());

Этап 2: Написание пакетного запроса

Теперь напишем текст запроса. Он будет состоять из двух частей:

  1. Помещение данных во временную таблицу: Мы берем переданную ТЗ_Товары и кладем её в temp-таблицу.
  2. Получение конечных данных: Мы соединяем временную таблицу с регистром сведений.

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


Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТЗ.Номенклатура КАК Номенклатура,
    |   ТЗ.НомерСтроки КАК НомерСтроки
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |   &ТЗ_Товары КАК ТЗ
    |
    |ИНДЕКСИРОВАТЬ ПО
    |   Номенклатура
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |   ВТ_ИсходныеДанные.НомерСтроки КАК НомерСтроки,
    |   ВТ_ИсходныеДанные.Номенклатура КАК Номенклатура,
    |   ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Цена
    |ИЗ
    |   ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
    |       ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
    |               &Период,
    |               Номенклатура В
    |                   (ВЫБРАТЬ
    |                       Т.Номенклатура
    |                   ИЗ
    |                       ВТ_ИсходныеДанные КАК Т)) КАК ЦеныНоменклатурыСрезПоследних
    |       ПО ВТ_ИсходныеДанные.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
    |
    |УПОРЯДОЧИТЬ ПО
    |   НомерСтроки";

Важные моменты в этом коде:

Этап 3: Обработка результата

После выполнения запроса у нас есть выборка, в которой содержатся цены для каждой строки. Нам остается только заполнить исходную табличную часть. Поскольку мы выбирали и упорядочивали данные по НомерСтроки, сопоставление будет простым.


Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();

// Вариант А: Если порядок строк гарантированно совпадает (благодаря УПОРЯДОЧИТЬ ПО)
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    Если Выборка.Следующий() Тогда
        // Проверка на всякий случай
        Если СтрокаТЧ.НомерСтроки = Выборка.НомерСтроки Тогда
            СтрокаТЧ.Цена = Выборка.Цена;
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

// Вариант Б: Более надежный поиск строки (если порядок мог сбиться)
// В этом случае цикл лучше делать по выборке
Пока Выборка.Следующий() Цикл
    // Находим строку в ТЧ по идентификатору или номеру
    НайденаяСтрока = Объект.Товары.Найти(Выборка.НомерСтроки, "НомерСтроки");
    Если НайденаяСтрока <> Неопределено Тогда
        НайденаяСтрока.Цена = Выборка.Цена;
    КонецЕсли;
КонецЦикла;

Преимущества использования пакетных запросов

Подводя итог, давайте выделим ключевые плюсы данного подхода:

Используйте объект МенеджерВременныхТаблиц для любых операций, где требуется соединить данные из оперативной памяти (табличные части, массивы) с данными базы данных. Это признак качественного и производительного кода.

← На главную