Как правильно реализовать установку флажков в зависимости от состояния объектов без запросов в цикле

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

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

Почему запрос внутри цикла — это критическая ошибка производительности?

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

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

Отказ от СтрЗаменить в пользу параметров запроса

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


ТекстЗапроса = "ВЫБРАТЬ ... ГДЕ ДокументБух.Номер = ""&Номер""";
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&Номер", "З-21842");

Выясним причину, почему так делать нельзя. Когда вы используете СтрЗаменить, текст запроса каждый раз меняется. СУБД (SQL Server или PostgreSQL) воспринимает каждый такой запрос как абсолютно новый и тратит ресурсы на его компиляцию. Если же использовать метод УстановитьПараметр, текст запроса остается неизменным, и СУБД кэширует план выполнения, что значительно ускоряет работу при повторных вызовах.

Решение №1: Пакетный запрос с использованием массива номеров

Рассмотрим правильный алгоритм реализации задачи. Сначала мы должны собрать все номера заявок из нашего бизнес-процесса, по которым нужно проверить оплату, в один массив.

Разберем по шагам реализацию этого метода:

  1. Обходим бизнес-процесс (или его задачи) и заполняем массив СписокНомеров.
  2. Передаем этот массив в запрос к внешней базе через параметр.
  3. В тексте запроса используем условие В (&СписокНомеров).
  4. Полученную таблицу значений индексируем для быстрого поиска.

Пример кода для реализации этого подхода:


// 1. Собираем номера для проверки
МассивНомеров = Новый Массив;
Для Каждого СтрокаЗаявки Из СписокЗаявокБП Цикл
    Если ЗначениеЗаполнено(СтрокаЗаявки.НомерЗаявки) Тогда
        МассивНомеров.Добавить(СтрокаЗаявки.НомерЗаявки);
    КонецЕсли;
КонецЦикла;

// 2. Делаем ОДИН запрос к внешней базе
ТекстЗапроса = 
"ВЫБРАТЬ
|   ДокументБух.Номер КАК Номер,
|   ВЫБОР 
|       КОГДА ДокументБух.Состояние = ЗНАЧЕНИЕ(Перечисление.Состояния.Оплачено) ТОГДА ИСТИНА
|       ИНАЧЕ ЛОЖЬ 
|   КОНЕЦ КАК Оплачено
|ИЗ
|   Документ.ДокументБух КАК ДокументБух
|ГДЕ
|   ДокументБух.Номер В (&МассивНомеров)
|   И ДокументБух.Дата МЕЖДУ &НачалоПериода И &КонецПериода";

ЗапросВнешний = Новый Запрос(ТекстЗапроса);
ЗапросВнешний.УстановитьПараметр("МассивНомеров", МассивНомеров);
ЗапросВнешний.УстановитьПараметр("НачалоПериода", НачалоГода(ТекущаяДата()));
ЗапросВнешний.УстановитьПараметр("КонецПериода", КонецГода(ТекущаяДата()));

// Предположим, функция выполняет запрос в базе Бухгалтерии и возвращает ТЗ
ТЗ_Результаты = ВыполнитьЗапросКВнешнейБазе(ЗапросВнешний);

// 3. Индексируем ТЗ для моментального поиска в памяти
ТЗ_Результаты.Индексы.Добавить("Номер");

// 4. Снова обходим заявки и ставим флажки
Для Каждого СтрокаЗаявки Из СписокЗаявокБП Цикл
    ПараметрыПоиска = Новый Структура("Номер", СтрокаЗаявки.НомерЗаявки);
    НайденныеСтроки = ТЗ_Результаты.НайтиСтроки(ПараметрыПоиска);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        СтрокаЗаявки.Оплачено = НайденныеСтроки[0].Оплачено;
    КонецЕсли;
КонецЦикла;

Решение №2: Использование Схемы запроса (для динамических условий)

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

Важный нюанс: при работе с оператором В (&Список) помните, что у СУБД есть ограничение на количество элементов в списке (обычно около 1000). Если у вас 10 000 заявок, лучше разбивать массив на порции по 500-1000 штук и выполнять запрос несколько раз, объединяя результаты.

Оптимизация работы с датами и индексами

Проанализируем еще одну ошибку в исходном тексте автора: ГОД(ДокументБух.Дата) = 2019. Использование функций над полями базы данных в секции ГДЕ (таких как ГОД, МЕСЯЦ, ПОДСТРОКА) делает невозможным использование индексов. СУБД будет вынуждена прочитать все документы за все года, вычислить год для каждого и только потом сравнить.

Как правильно: Всегда используйте сравнение по границам периода. Вместо функции ГОД используйте Дата МЕЖДУ &НачалоПериода И &КонецПериода. В этом случае запрос отработает мгновенно, так как поле Дата в 1С всегда индексировано по умолчанию.

Дополнительные советы для «профессионального» кода

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

Таким образом, соблюдая эти несложные правила (использование параметров, пакетная выборка данных, отказ от функций в условиях ГДЕ и индексация результатов), вы сможете превратить медленную и нестабильную обработку в быстрый и надежный инструмент.

← На главную