Одной из самых распространенных ошибок при разработке на платформе 1С, которая приводит к значительному падению производительности, является выполнение запросов внутри цикла. Эту ситуацию мы часто наблюдаем, когда необходимо получить дополнительные данные (например, цены, остатки или реквизиты) для каждой строки табличной части или выборки документов.
В этой статье мы подробно разберем, почему подход "запрос в цикле" считается критической ошибкой, и рассмотрим профессиональные способы решения этой задачи с помощью пакетных запросов и временных таблиц.
Давайте проанализируем ситуацию. Представьте, что у вас есть табличная часть документа с номенклатурой, содержащая 1000 строк. Если мы используем классический (и неверный) подход, алгоритм выглядит так:
Это означает, что платформа 1С совершит 1000 обращений к серверу СУБД (SQL). Каждое обращение требует времени на передачу данных по сети, компиляцию запроса и его выполнение. Даже если один запрос выполняется 0.01 секунды, тысяча запросов займет ощутимое время, блокируя работу пользователя.
Рассмотрим, как выглядит этот неправильный код, чтобы знать врага в лицо:
// НЕПРАВИЛЬНЫЙ ПОДХОД (Запрос в цикле)
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура = &Номенклатура) КАК ЦеныНоменклатурыСрезПоследних";
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("Номенклатура", СтрокаТЧ.Номенклатура);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
СтрокаТЧ.Цена = Выборка.Цена;
КонецЕсли;
КонецЦикла;
Теперь перейдем к правильным решениям. Основная идея оптимизации — получить все необходимые данные одним единственным запросом до или во время обработки данных, а затем распределить их по строкам.
Если исходных данных немного, можно собрать ссылки на интересующие нас объекты (например, Номенклатуру) в массив и передать его в запрос.
Разберем этот метод по шагам:
ГДЕ Номенклатура В (&МассивНоменклатуры).Однако этот метод имеет ограничения. Если в массиве будут тысячи элементов, сам текст SQL-запроса станет слишком большим, что тоже может замедлить работу. Поэтому для обработки табличных частей документов наиболее эффективным является второй способ.
Этот способ является стандартом профессиональной разработки в 1С. Мы помещаем исходную таблицу (из 1С) во временную таблицу (в СУБД), а затем одним запросом соединяем её с данными регистра (например, с ценами).
Давайте посмотрим, как реализовать этот алгоритм. Нам понадобятся объекты МенеджерВременныхТаблиц и Запрос.
Сначала создадим менеджер временных таблиц и поместим в него нашу табличную часть. Обратите внимание: для метода ВыполнитьПакет или простого выполнения запроса с временными таблицами, таблица-источник должна быть типизирована или передана как параметр.
// Создаем запрос и менеджер
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Передаем табличную часть как параметр
// Важно: Если мы работаем с ТЧ документа на форме, лучше сначала выгрузить её в ТаблицуЗначений
ТЗ_Товары = Объект.Товары.Выгрузить();
Запрос.УстановитьПараметр("ТЗ_Товары", ТЗ_Товары);
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Теперь напишем текст запроса. Он будет состоять из двух частей:
ТЗ_Товары и кладем её в temp-таблицу.Проанализируем код запроса:
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура,
| ТЗ.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ТЗ_Товары КАК ТЗ
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ИсходныеДанные.НомерСтроки КАК НомерСтроки,
| ВТ_ИсходныеДанные.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Цена
|ИЗ
| ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
| &Период,
| Номенклатура В
| (ВЫБРАТЬ
| Т.Номенклатура
| ИЗ
| ВТ_ИсходныеДанные КАК Т)) КАК ЦеныНоменклатурыСрезПоследних
| ПО ВТ_ИсходныеДанные.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
|
|УПОРЯДОЧИТЬ ПО
| НомерСтроки";
Важные моменты в этом коде:
ПОМЕСТИТЬ для создания таблицы ВТ_ИсходныеДанные внутри оперативной памяти сервера СУБД.ИНДЕКСИРОВАТЬ ПО Номенклатура. Это критически важно для скорости соединения таблиц, особенно на больших объемах.Номенклатура В (ВЫБРАТЬ ... ИЗ ВТ_ИсходныеДанные). Это заставляет SQL-сервер рассчитывать срез только для нужных нам товаров, а не для всей базы, что колоссально ускоряет работу.ЛЕВОЕ СОЕДИНЕНИЕ, чтобы не потерять строки из исходной таблицы, даже если цены на товар не найдено (в этом случае ЕСТЬNULL вернет 0).После выполнения запроса у нас есть выборка, в которой содержатся цены для каждой строки. Нам остается только заполнить исходную табличную часть. Поскольку мы выбирали и упорядочивали данные по НомерСтроки, сопоставление будет простым.
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
// Вариант А: Если порядок строк гарантированно совпадает (благодаря УПОРЯДОЧИТЬ ПО)
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Если Выборка.Следующий() Тогда
// Проверка на всякий случай
Если СтрокаТЧ.НомерСтроки = Выборка.НомерСтроки Тогда
СтрокаТЧ.Цена = Выборка.Цена;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Вариант Б: Более надежный поиск строки (если порядок мог сбиться)
// В этом случае цикл лучше делать по выборке
Пока Выборка.Следующий() Цикл
// Находим строку в ТЧ по идентификатору или номеру
НайденаяСтрока = Объект.Товары.Найти(Выборка.НомерСтроки, "НомерСтроки");
Если НайденаяСтрока <> Неопределено Тогда
НайденаяСтрока.Цена = Выборка.Цена;
КонецЕсли;
КонецЦикла;
Подводя итог, давайте выделим ключевые плюсы данного подхода:
&Период), мы гарантируем, что данные согласованы. В цикле же, теоретически, пока мы дойдем до 1000-й строки, данные в регистре могли бы измениться другим пользователем (хотя для среза на дату это менее актуально, но важно для остатков).Используйте объект МенеджерВременныхТаблиц для любых операций, где требуется соединить данные из оперативной памяти (табличные части, массивы) с данными базы данных. Это признак качественного и производительного кода.