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