Как программно изменить родителя у группы контрагентов в 1С и почему запрос может возвращать пустой результат

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

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

Разбор ситуации: почему цикл не выполняется

Рассмотрим ситуацию, когда разработчик пишет код для обхода результатов запроса, но при отладке обнаруживает, что программа «проскакивает» цикл Для Каждого. Проанализируем исходный фрагмент кода, который вызвал затруднение:


Для Каждого кнтр из результат цикл 
    объектспр = кнтр.ссылка.получитьобъект();
    объектспр.родитель = ОбщийСписок;
    объектспр.записать();
КонецЦикла;

Если отладчик сразу переходит на строку КонецЦикла, это однозначно указывает на то, что коллекция результат пуста. В данном контексте крайне важно проверить сам запрос и параметры, которые в него передаются. Выясним причину, изучив структуру запроса:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
               |    Контрагенты.Ссылка КАК Ссылка
               |ИЗ
               |    Справочник.Контрагенты КАК Контрагенты
               |ГДЕ
               |    Контрагенты.Родитель = &Родитель
               |    И Контрагенты.ПометкаУдаления = ЛОЖЬ
               |    И Контрагенты.ДатаСоздания < &ДатаСоздания";

Запрос.УстановитьПараметр("Родитель", Справочники.Контрагенты.НайтиПоКоду("Ц0005008"));      
Запрос.УстановитьПараметр("ДатаСоздания", ДобавитьМесяц(ТекущаяДата(), -3));
Результат = Запрос.Выполнить().Выгрузить();

Основные причины, по которым такой запрос может не вернуть данных:

  1. Некорректное условие по дате: Проверьте логику сравнения. Если мы ищем контрагентов, созданных более 3 месяцев назад, условие ДатаСоздания < &ДатаСоздания верно. Если же нужны «новые», знак должен быть обратным.
  2. Поиск по коду: Использование НайтиПоКоду("Ц0005008") может вернуть пустую ссылку, если в базе изменен префикс или код был переименован. Если &Родитель пустой, запрос не найдет элементы.
  3. Данные уже обработаны: Часто бывает так, что при повторном запуске обработки все объекты уже соответствуют условиям (например, у них уже изменен родитель), и запрос закономерно ничего не находит.

Правильная работа с выборкой данных

Рассмотрим более профессиональный подход к написанию этого кода. Использование метода Выгрузить() оправдано только для небольших наборов данных, так как он создает таблицу значений в оперативной памяти. Если контрагентов тысячи, это приведет к избыточному потреблению ресурсов. Разберем, как сделать код более надежным.

Вместо выгрузки в таблицу будем использовать объект ВыборкаИзРезультатаЗапроса. Это позволит нам обрабатывать данные последовательно.


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

// Вместо жесткого поиска по коду лучше использовать ссылку на элемент, 
// выбранный пользователем на форме обработки
Запрос.УстановитьПараметр("СтарыйРодитель", СсылкаНаСтаруюГруппу);      
Запрос.УстановитьПараметр("ГраничнаяДата", ДобавитьМесяц(ТекущаяДата(), -3));

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    // Проанализируем ситуацию: нужно ли менять родителя?
    Если Выборка.Родитель = ОбщийСписок Тогда
        Продолжить; // Объект уже на месте
    КонецЕсли;

    Попытка
        ОбъектСпр = Выборка.Ссылка.ПолучитьОбъект();
        ОбъектСпр.Родитель = ОбщийСписок;
        
        // Используем ОбменДанными.Загрузка = Истина, если не нужно запускать проверки
        // в модуле объекта (например, при техническом переносе)
        // ОбъектСпр.ОбменДанными.Загрузка = Истина;
        
        ОбъектСпр.Записать();
    Исключение
        Сообщить("Не удалось изменить объект: " + Выборка.Ссылка + " Ошибка: " + ОписаниеОшибки());
    КонецПопытки;
КонецЦикла;

Оптимизация производительности и блокировки

При массовой записи объектов важно помнить о нагрузке на сервер. Посмотрим на критические моменты, которые помогут ускорить работу:

  1. Минимизация записи: Вызов метода Записать() — это тяжелая операция. Она инициирует транзакцию, вызывает проверки, записывает данные в журнал регистрации и обновляет индексы. Всегда проверяйте, изменился ли реквизит, прежде чем сохранять объект.
  2. Индексирование полей: Если вы часто делаете выборки по полю ДатаСоздания, убедитесь, что в конфигураторе для этого реквизита установлено свойство Индексировать. Без индекса СУБД будет выполнять полное сканирование таблицы, что крайне медленно.
  3. Использование транзакций: Для ускорения записи можно объединять операции в пакеты. Рассмотрим пример с использованием транзакции для каждых 100 элементов:

Счетчик = 0;
НачатьТранзакцию();

Пока Выборка.Следующий() Цикл
    Счетчик = Счетчик + 1;
    
    // ... логика получения и изменения объекта ...
    ОбъектСпр.Записать();
    
    Если Счетчик % 100 = 0 Тогда
        ЗафиксироватьТранзакцию();
        НачатьТранзакцию();
    КонецЕсли;
КонецЦикла;

Если ТранзакцияАктивна() Тогда
    ЗафиксироватьТранзакцию();
КонецЕсли;

Важно: Слишком длинные транзакции (например, на весь цикл из 10 000 элементов) могут заблокировать работу других пользователей в базе. Разделение на пакеты — это «золотая середина» между скоростью и стабильностью системы.

Поиск объектов по коду: почему это плохо

В примере автора использовался метод Справочники.Контрагенты.НайтиПоКоду("Ц0005008"). Разберем, почему в реальных задачах так делать не стоит:

Рекомендация: Вместо жесткого прописывания кодов используйте параметры обработки, где пользователь может интерактивно выбрать нужную папку, или храните ссылки в Предопределенных элементах, если это позволяет структура конфигурации.

Заключение

Подведем итог. Чтобы ваша обработка по переносу контрагентов работала корректно, всегда начинайте с проверки результата запроса в Консоли запросов. Убедитесь, что параметры (дата, родитель) передаются правильно. При написании самого кода отдавайте предпочтение Выборке, используйте блоки Попытка...Исключение для защиты от блокировок и не забывайте о проверке на избыточную запись данных. Эти простые правила сделают ваш код профессиональным и надежным.

← На главную