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