Как правильно написать произвольный алгоритм в обработке подбора и обработки объектов 1С, чтобы избежать задвоения данных?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При использовании стандартных типовых обработок (таких как «Универсальный подбор и обработка объектов» или «Групповая обработка справочников и документов») разработчики часто сталкиваются с проблемой: при заполнении табличных частей или реквизитов данные вносятся по несколько раз, а одна и та же запись повторяется ровно столько раз, сколько элементов было выбрано.

В этой статье мы подробно разберем причину возникновения такой ошибки, проанализируем некорректный код и рассмотрим правильный подход к написанию произвольного алгоритма в 1С.

Причина возникновения проблемы и задвоения данных

Проанализируем механику работы инструмента групповой обработки объектов. Механизм обработки устроен следующим образом: на этапе отбора формируется список ссылок, а затем для каждого элемента из списка последовательно выполняется переданный произвольный алгоритм. В контекст этого алгоритма платформа автоматически передает переменную Объект (или ТекущийОбъект).

Основная ошибка заключается в попытке выполнить внутри произвольного алгоритма дополнительный запрос к базе данных и перебрать его выборку в цикле. В результате получается вложенный цикл:

  1. Внешний цикл обработки перебирает отобранные элементы справочника или документа.
  2. На каждом шаге внешнего цикла запускается ваш запрос, который снова выбирает все элементы из базы.
  3. Внутренний цикл по выборке запроса начинает повторно модифицировать объект и добавлять в него новые строки.

Это не только приводит к многократному дублированию строк в табличной части, но и критически снижает производительность обработки.

Разбор некорректного кода

Посмотрим на пример кода, в котором допускается эта ошибка:


СписокКонтрагентов = Новый СписокЗначений;
СписокКонтрагентов.Добавить("Альянс Белгород 2 РС ООО");

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗЛИЧНЫЕ ПЕРВЫЕ 2
|    Компьютеры.Ссылка,
|    Компьютеры.Контрагент КАК Контрагент
|ИЗ
|    Справочник.Компьютеры КАК Компьютеры
|ГДЕ
|     Компьютеры.Контрагент.Наименование В(&Контрагент)
|СГРУППИРОВАТЬ ПО
|    Компьютеры.Ссылка,
|    Компьютеры.Контрагент";

Запрос.УстановитьПараметр("Контрагент", СписокКонтрагентов);
РезультатЗапросаКомпьютеры = Запрос.Выполнить().Выбрать();

Если РезультатЗапросаКомпьютеры.Количество() > 0 Тогда
    Пока РезультатЗапросаКомпьютеры.Следующий() Цикл
        Источник = РезультатЗапросаКомпьютеры.Ссылка;
        ЭлементОбработан = Ложь;
        Если Не ЭлементОбработан Тогда
            НовоеСвойство = Объект.Свойства.Добавить();
            НовоеСвойство.ВидСвойства = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Дата снятия с учета");
            НовоеСвойство.ЗначениеСвойства = НачалоГода(ТекущаяДата());
            Сообщить(СОКРЛП(РезультатЗапросаКомпьютеры.Ссылка.Контрагент) + " " + СОКРЛП(РезультатЗапросаКомпьютеры.Ссылка));
            Объект.Записать();
            ЭлементОбработан = Истина;
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

В приведенном фрагменте код запроса и внутренний цикл Пока РезультатЗапросаКомпьютеры.Следующий() Цикл являются избыточными. Попытка сбросить флаг ЭлементОбработан внутри цикла также не предотвращает задвоение, так как сам цикл выполняется повторно на каждой итерации внешней обработки.

Правильное решение: работа с текущим объектом

Выясним, как написать этот алгоритм правильно. Поскольку обработка уже выбрала объект и предоставила к нему доступ через переменную Объект (или ТекущийОбъект), нам не нужно повторно выполнять запрос. Мы должны производить манипуляции непосредственно с текущей записью.

Правильный вариант произвольного алгоритма выглядит следующим образом:


// В качестве источника используем текущий объект из контекста обработки
ВидСвойства = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Дата снятия с учета");

// Добавляем новую строку в табличную часть текущего объекта
НовоеСвойство = Объект.Свойства.Добавить();
НовоеСвойство.ВидСвойства = ВидСвойства;
НовоеСвойство.ЗначениеСвойства = НачалоГода(ТекущаяДата());

Сообщить(СОКРЛП(Объект.Контрагент) + " " + СОКРЛП(Объект.Ссылка));

Обратите внимание: если в настройках самой групповой обработки включена опция автоматического сохранения изменений, вызов Объект.Записать() внутри произвольного алгоритма делать не нужно. Ручной вызов метода записи может привести к дублированию событий записи и лишней нагрузке на систему.

Защита от повторного добавления строк

Чтобы исключить задвоение данных при повторном запуске алгоритма, рекомендуется предварительно проверять наличие нужной записи в табличной части с помощью метода НайтиСтроки().

Рассмотрим по шагам безопасный вариант обновления реквизита:


ВидСвойства = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Дата снятия с учета");

Если ЗначениеЗаполнено(ВидСвойства) Тогда
    // Формируем структуру отбора для поиска существующей строки
    Отбор = Новый Структура;
    Отбор.Вставить("ВидСвойства", ВидСвойства);
    
    НайденныеСтроки = Объект.Свойства.НайтиСтроки(Отбор);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        // Если строка уже есть — используем её
        СтрокаСвойства = НайденныеСтроки[0];
    Иначе
        // Если строки нет — добавляем новую
        СтрокаСвойства = Объект.Свойства.Добавить();
        СтрокаСвойства.ВидСвойства = ВидСвойства;
    КонецЕсли;
    
    СтрокаСвойства.ЗначениеСвойства = НачалоГода(ТекущаяДата());
КонецЕсли;

Подведем итоги

  1. Избегайте выполнения внутренних запросов и циклов выборки внутри произвольных алгоритмов групповой обработки.
  2. Вместо построения запросов обращайтесь напрямую к контексту обрабатываемого элемента — переменным Объект или ТекущийОбъект.
  3. Перед вызовом метода .Добавить() для табличных частей проверяйте существование строк через НайтиСтроки(), чтобы предотвратить дублирование данных при повторных запусках.
← На главную