В процессе работы с базами 1С (ERP, Комплексная автоматизация, Управление торговлей или CRM) часто возникает задача массовой нормализации данных. Одна из типичных задач — заполнить реквизит основного контактного лица у партнеров или контрагентов, если он остался пустым. При этом логика назначения обычно следующая: если у партнера зарегистрирован только один контакт, выбираем его; если несколько — берем первый по алфавиту; если основной контакт уже назначен, объект не изменяем.
Разберем, с какими сложностями можно столкнуться при реализации этой задачи через запросы и обработку данных, а также рассмотрим надежные варианты решения.
Прежде чем приступать к написанию кода, проанализируем ключевые нюансы работы платформы 1С:
МАКСИМУМ(КонтактныеЛица.Ссылка) группирует записи по внутреннему идентификатору (UUID), а не по алфавиту наименований или порядку добавления. Это приводит к назначению случайного контакта при наличии нескольких вариантов.ГДЕ Владелец.CRM_ОсновноеКонтактноеЛицо = ЗНАЧЕНИЕ(...), то после первого успешного выполнения обработки данные перезаписываются. Повторный запуск такого запроса вернет пустую выборку, так как пустых значений в базе уже не осталось.Рассмотрим вариант, в котором запрос получает упорядоченный список контактных лиц с группировкой по владельцу через конструкцию ИТОГИ ПО. В программном коде мы открываем выборку верхнего уровня по партнерам, а для детальных записей вызываем метод Следующий() ровно один раз. Это гарантирует выборку первого контакта по алфавиту.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КонтактныеЛица.Ссылка КАК КонтактноеЛицо,
| КонтактныеЛица.Владелец КАК Партнер
|ИЗ
| Справочник.КонтактныеЛицаПартнеров КАК КонтактныеЛица
|ГДЕ
| НЕ КонтактныеЛица.ПометкаУдаления
| И НЕ КонтактныеЛица.Владелец.ПометкаУдаления
| И (КонтактныеЛица.Владелец.CRM_ОсновноеКонтактноеЛицо ЕСТЬ NULL
| ИЛИ КонтактныеЛица.Владелец.CRM_ОсновноеКонтактноеЛицо = ЗНАЧЕНИЕ(Справочник.КонтактныеЛицаПартнеров.ПустаяСсылка))
|УПОРЯДОЧИТЬ ПО
| КонтактныеЛица.Владелец,
| КонтактныеЛица.Наименование
|ИТОГИ ПО
| Партнер";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаПартнеры = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаПартнеры.Следующий() Цикл
ВыборкаДетали = ВыборкаПартнеры.Выбрать();
// Берем только первую запись из детальной выборки
Если ВыборкаДетали.Следующий() Тогда
ОбъектПартнер = ВыборкаПартнеры.Партнер.ПолучитьОбъект();
ОбъектПартнер.CRM_ОсновноеКонтактноеЛицо = ВыборкаДетали.КонтактноеЛицо;
// Отключаем лишние проверки при пакетной обработке
ОбъектПартнер.ОбменДанными.Загрузка = Истина;
ОбъектПартнер.Записать();
КонецЕсли;
КонецЦикла;
Посмотрим на альтернативный способ, когда выборка изначально формируется плоской и содержит строго одну строку на каждого партнера. Для этого сформируем временную таблицу с минимальным наименованием контакта, а затем свяжем ее с исходным справочником.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КонтактныеЛица.Владелец КАК Партнер,
| МИНИМУМ(КонтактныеЛица.Наименование) КАК МинНаименование
|ПОМЕСТИТЬ ВТ_ПервыеКонтакты
|ИЗ
| Справочник.КонтактныеЛицаПартнеров КАК КонтактныеЛица
|ГДЕ
| НЕ КонтактныеЛица.ПометкаУдаления
| И НЕ КонтактныеЛица.Владелец.ПометкаУдаления
| И (КонтактныеЛица.Владелец.CRM_ОсновноеКонтактноеЛицо ЕСТЬ NULL
| ИЛИ КонтактныеЛица.Владелец.CRM_ОсновноеКонтактноеЛицо = ЗНАЧЕНИЕ(Справочник.КонтактныеЛицаПартнеров.ПустаяСсылка))
|СГРУППИРОВАТЬ ПО
| КонтактныеЛица.Владелец
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Партнер КАК Партнер,
| МИНИМУМ(КонтактныеЛица.Ссылка) КАК КонтактноеЛицо
|ИЗ
| ВТ_ПервыеКонтакты КАК ВТ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.КонтактныеЛицаПартнеров КАК КонтактныеЛица
| ПО ВТ.Партнер = КонтактныеЛица.Владелец
| И ВТ.МинНаименование = КонтактныеЛица.Наименование
|ГДЕ
| НЕ КонтактныеЛица.ПометкаУдаления
|СГРУППИРОВАТЬ ПО
| ВТ.Партнер";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ОбъектПартнер = Выборка.Партнер.ПолучитьОбъект();
ОбъектПартнер.CRM_ОсновноеКонтактноеЛицо = Выборка.КонтактноеЛицо;
ОбъектПартнер.ОбменДанными.Загрузка = Истина;
ОбъектПартнер.Записать();
КонецЦикла;
Разберем практические рекомендации, которые помогут избежать блокировок и ускорят выполнение обработки в рабочей базе:
ОбменДанными.Загрузка: Установка флага Объект.ОбменДанными.Загрузка = Истина перед вызовом Записать() отключает прикладные проверки в модуле объекта и подписках на события. Это предотвращает возникновение ошибок валидации других незаполненных реквизитов и существенно ускоряет процесс.НачатьТранзакцию() и ЗафиксироватьТранзакцию().Партнеры (управленческий учет), а не Контрагенты (регламентированные данные).