При выполнении проверок в документах начинающие разработчики часто совершают распространенную ошибку — вызывают запрос к базе данных внутри цикла обхода строк табличной части. Подобный подход приводит к критическому падению производительности: если в документе сотни или тысячи строк, платформа выполнит сотни или тысячи отдельных обращений к СУБД. Проанализируем, как правильно оптимизировать такой код и избавиться от запроса в цикле.
Рассмотрим исходную ситуацию: при проведении документа ПриходнаяНакладная необходимо проверить, относится ли номенклатура из табличной части Состав к алкогольной продукции (информация хранится в регистре сведений ВидыАлкогольнойПродукции). Если позиция является алкогольной, но в строке не заполнен реквизит Производитель, проведение документа должно отменяться с выводом сообщения пользователю.
Если выполнять вызов функции с запросом на каждой итерации цикла Для каждого Строка Из Состав Цикл, сервер базы данных получает огромную избыточную нагрузку. Чтобы этого избежать, все проверки необходимо выполнять одним пакетным запросом.
Разберем самый простой вариант оптимизации. Вместо того чтобы запрашивать данные по одной позиции номенклатуры, соберем все проверяемые элементы в массив и передадим их в запрос с использованием оператора В (&Массив).
Перед формированием запроса применим предварительную фильтрацию: отоберем только те строки, в которых реквизит Производитель действительно не заполнен. Если у всей номенклатуры в документе производитель уже указан, запрос к базе данных вообще не потребуется выполнять.
Рассмотрим пример реализации:
// 1. Собираем номенклатуру из строк, где не заполнен производитель
МассивПроверяемойНоменклатуры = Новый Массив;
Для Каждого ТекСтрока Из Состав Цикл
Если НЕ ЗначениеЗаполнено(ТекСтрока.Производитель) И ЗначениеЗаполнено(ТекСтрока.Номенклатура) Тогда
МассивПроверяемойНоменклатуры.Добавить(ТекСтрока.Номенклатура);
КонецЕсли;
КонецЦикла;
// Если незаполненных производителей нет, сразу выходим из проверки
Если МассивПроверяемойНоменклатуры.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// 2. Выполняем один запрос к регистру сведений по всем отобранным позициям
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВидыАлкогольнойПродукции.Номенклатура КАК Номенклатура
|ИЗ
| РегистрСведений.ВидыАлкогольнойПродукции КАК ВидыАлкогольнойПродукции
|ГДЕ
| ВидыАлкогольнойПродукции.Номенклатура В(&МассивНоменклатуры)";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивПроверяемойНоменклатуры);
РезультатЗапроса = Запрос.Выполнить();
// 3. Если результат не пустой, значит в документе есть алкогольная продукция без производителя
Если НЕ РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить("У номенклатуры " + Выборка.Номенклатура + " не указан производитель!");
КонецЦикла;
Отказ = Истина;
КонецЕсли;
Первый способ позволяет быстро узнать, у какой именно номенклатуры отсутствует производитель. Однако если одна и та же номенклатура встречается в документе несколько раз, пользователю будет сложно понять, в какой конкретно строке допущена ошибка. Чтобы вывести номера проблемных строк, загрузим табличную часть документа во временную таблицу запроса.
Проанализируем алгоритм этого решения:
НомерСтроки, Номенклатура и Производитель из табличной части в таблицу значений.ВидыАлкогольнойПродукции.Производитель не заполнен.Посмотрим на пример кода:
// 1. Выгружаем колонки табличной части
ТЗСостав = Состав.Выгрузить(, "НомерСтроки, Номенклатура, Производитель");
// 2. Формируем запрос с временной таблицей
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗСостав.НомерСтроки КАК НомерСтроки,
| ТЗСостав.Номенклатура КАК Номенклатура,
| ТЗСостав.Производитель КАК Производитель
|ПОМЕСТИТЬ ВТ_Состав
|ИЗ
| &ТЗСостав КАК ТЗСостав
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Состав.НомерСтроки КАК НомерСтроки,
| ВТ_Состав.Номенклатура КАК Номенклатура
|ИЗ
| ВТ_Состав КАК ВТ_Состав
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ВидыАлкогольнойПродукции КАК ВидыАлкогольнойПродукции
| ПО ВТ_Состав.Номенклатура = ВидыАлкогольнойПродукции.Номенклатура
|ГДЕ
| НЕ ЗНАЧЕНИЕЗАПОЛНЕНО(ВТ_Состав.Производитель)";
Запрос.УстановитьПараметр("ТЗСостав", ТЗСостав);
РезультатЗапроса = Запрос.Выполнить();
Если НЕ РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
ТекстСообщения = СтрШаблон("В строке №%1 у алкогольной продукции (%2) не указан производитель!",
Выборка.НомерСтроки, Выборка.Номенклатура);
Сообщить(ТекстСообщения);
КонецЦикла;
Отказ = Истина;
КонецЕсли;
Выясним дополнительные аспекты, которые помогут сделать проверку еще более эффективной и устойчивой к ошибкам:
ОбработкаПроведения модуля документа. Если проверка выполняется в событии ПередЗаписью, обязательно проверяйте режим записи: РежимЗаписи = РежимЗаписиДокумента.Проведение, иначе пользователь не сможет сохранить даже черновик документа..Пустая(), который предназначен только для ссылочных типов, используйте системную функцию ЗначениеЗаполнено(). Она корректно обрабатывает неопределенные значения, составные типы данных и пустые ссылки.РезультатЗапроса.Пустой(). Это избавит от необходимости получать выборку, если ошибок не обнаружено.СообщениеПользователю вместо базовой функции Сообщить(). Это позволит привязать сообщение к конкретной строке и ячейке табличной части на форме.