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