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