В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача: обновить реквизиты множества объектов (например, заявок в бизнес-процессе) на основе данных из другой базы или таблицы. Одной из самых распространенных ошибок начинающих разработчиков является выполнение запроса к базе данных внутри цикла перебора объектов. Рассмотрим подробнее, почему этот подход считается «моветоном», и разберем «православные» способы решения данной задачи, которые обеспечат высокую скорость работы системы.
Проанализируем ситуацию: если у вас 1000 заявок и вы для каждой делаете отдельный запрос, система выполняет 1000 обращений к СУБД. Каждое обращение включает в себя установку соединения, передачу текста запроса, его компиляцию (составление плана выполнения), собственно поиск данных и возврат результата. Это создает колоссальную нагрузку на сервер и заставляет пользователя долго ждать завершения операции.
Вместо этого мы должны придерживаться правила: один цикл — один запрос. Нам нужно собрать все необходимые данные для условий поиска в массив, передать этот массив в запрос один раз, получить общую таблицу результатов и затем обработать ее в памяти.
Еще один важный момент, за который, как выразились на форуме, «бьют канделябром» — это использование функции СтрЗаменить для подстановки значений в текст запроса. Посмотрим на пример некорректного кода:
ТекстЗапроса = "ВЫБРАТЬ ... ГДЕ ДокументБух.Номер = ""&Номер""";
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&Номер", "З-21842");
Выясним причину, почему так делать нельзя. Когда вы используете СтрЗаменить, текст запроса каждый раз меняется. СУБД (SQL Server или PostgreSQL) воспринимает каждый такой запрос как абсолютно новый и тратит ресурсы на его компиляцию. Если же использовать метод УстановитьПараметр, текст запроса остается неизменным, и СУБД кэширует план выполнения, что значительно ускоряет работу при повторных вызовах.
Рассмотрим правильный алгоритм реализации задачи. Сначала мы должны собрать все номера заявок из нашего бизнес-процесса, по которым нужно проверить оплату, в один массив.
Разберем по шагам реализацию этого метода:
СписокНомеров.В (&СписокНомеров).Пример кода для реализации этого подхода:
// 1. Собираем номера для проверки
МассивНомеров = Новый Массив;
Для Каждого СтрокаЗаявки Из СписокЗаявокБП Цикл
Если ЗначениеЗаполнено(СтрокаЗаявки.НомерЗаявки) Тогда
МассивНомеров.Добавить(СтрокаЗаявки.НомерЗаявки);
КонецЕсли;
КонецЦикла;
// 2. Делаем ОДИН запрос к внешней базе
ТекстЗапроса =
"ВЫБРАТЬ
| ДокументБух.Номер КАК Номер,
| ВЫБОР
| КОГДА ДокументБух.Состояние = ЗНАЧЕНИЕ(Перечисление.Состояния.Оплачено) ТОГДА ИСТИНА
| ИНАЧЕ ЛОЖЬ
| КОНЕЦ КАК Оплачено
|ИЗ
| Документ.ДокументБух КАК ДокументБух
|ГДЕ
| ДокументБух.Номер В (&МассивНомеров)
| И ДокументБух.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
ЗапросВнешний = Новый Запрос(ТекстЗапроса);
ЗапросВнешний.УстановитьПараметр("МассивНомеров", МассивНомеров);
ЗапросВнешний.УстановитьПараметр("НачалоПериода", НачалоГода(ТекущаяДата()));
ЗапросВнешний.УстановитьПараметр("КонецПериода", КонецГода(ТекущаяДата()));
// Предположим, функция выполняет запрос в базе Бухгалтерии и возвращает ТЗ
ТЗ_Результаты = ВыполнитьЗапросКВнешнейБазе(ЗапросВнешний);
// 3. Индексируем ТЗ для моментального поиска в памяти
ТЗ_Результаты.Индексы.Добавить("Номер");
// 4. Снова обходим заявки и ставим флажки
Для Каждого СтрокаЗаявки Из СписокЗаявокБП Цикл
ПараметрыПоиска = Новый Структура("Номер", СтрокаЗаявки.НомерЗаявки);
НайденныеСтроки = ТЗ_Результаты.НайтиСтроки(ПараметрыПоиска);
Если НайденныеСтроки.Количество() > 0 Тогда
СтрокаЗаявки.Оплачено = НайденныеСтроки[0].Оплачено;
КонецЕсли;
КонецЦикла;
Если количество заявок очень велико или условия отбора постоянно меняются, можно использовать объект СхемаЗапроса. Как было отмечено в сообщении 8 на форуме, это позволяет программно достраивать текст запроса, добавляя нужные отборы без ручной манипуляции со строками. Это более продвинутый и «чистый» способ формирования сложных динамических запросов.
Важный нюанс: при работе с оператором В (&Список) помните, что у СУБД есть ограничение на количество элементов в списке (обычно около 1000). Если у вас 10 000 заявок, лучше разбивать массив на порции по 500-1000 штук и выполнять запрос несколько раз, объединяя результаты.
Проанализируем еще одну ошибку в исходном тексте автора: ГОД(ДокументБух.Дата) = 2019. Использование функций над полями базы данных в секции ГДЕ (таких как ГОД, МЕСЯЦ, ПОДСТРОКА) делает невозможным использование индексов. СУБД будет вынуждена прочитать все документы за все года, вычислить год для каждого и только потом сравнить.
Как правильно: Всегда используйте сравнение по границам периода. Вместо функции ГОД используйте Дата МЕЖДУ &НачалоПериода И &КонецПериода. В этом случае запрос отработает мгновенно, так как поле Дата в 1С всегда индексировано по умолчанию.
Для того чтобы ваша обработка работала максимально эффективно, рекомендуем обратить внимание на следующие аспекты:
Объект.ОбменДанными.Загрузка = Истина. Это отключит лишние проверки в модулях объекта и подписках на события, что ускорит процесс записи в несколько раз.ТаблицуЗначений на клиенте для последующей передачи в другой запрос. Используйте менеджер временных таблиц и соединение таблиц прямо внутри одного запроса. Как сказано в сообщении 11, бизнес-процесс — это такая же таблица, которую можно соединить с таблицей оплат.Таким образом, соблюдая эти несложные правила (использование параметров, пакетная выборка данных, отказ от функций в условиях ГДЕ и индексация результатов), вы сможете превратить медленную и нестабильную обработку в быстрый и надежный инструмент.