В процессе разработки в среде 1С программисты часто сталкиваются с ситуацией, когда необходимо изменить не ресурс или реквизит, а именно измерение в уже существующей записи регистра сведений. Попытка сделать это «в лоб», через стандартный набор записей с установленным отбором, неизбежно приводит к ошибке: «Запись не верна! Значение поля не соответствует установленному отбору». Разберем подробно, почему так происходит, и рассмотрим правильные методики решения этой задачи.
Проанализируем техническую причину такого поведения платформы. Измерения в регистре сведений составляют уникальный ключ записи. Когда мы используем объект РегистрСведенийНаборЗаписей, мы устанавливаем фильтр (отбор) по конкретным значениям. Платформа 1С строго контролирует целостность этого набора. Если вы установили отбор по полю Назначение с конкретным значением «А», то 1С ожидает, что во всех записях этого набора в данном поле будет стоять именно «А».
Как только в цикле вы меняете Запись.Назначение = "Б", запись перестает соответствовать установленному фильтру набора. При попытке метода Записать() система видит этот конфликт и блокирует транзакцию, чтобы не допустить нарушения логической структуры индексов в базе данных. По сути, с точки зрения СУБД, изменение измерения — это не обновление (UPDATE) существующей строки, а удаление старой строки с одним ключом и создание новой с другим.
Если нам нужно изменить одну конкретную запись, использование объекта РегистрСведенийМенеджерЗаписи является самым простым и «чистым» способом. Рассмотрим алгоритм действий: мы должны «найти» старую запись, удалить ее и создать новую с измененным измерением. Платформа позволяет сделать это в рамках одного объекта менеджера.
Разберем пример кода для непериодического регистра:
// Создаем объект менеджера записи
МенеджерЗаписи = РегистрыСведений.НазначениеНоменклатуры.СоздатьМенеджерЗаписи();
// Устанавливаем значения ВСЕХ старых измерений для поиска записи
МенеджерЗаписи.Номенклатура = Выборка.Номенклатура;
МенеджерЗаписи.Назначение = Выборка.Назначение;
// Читаем запись из базы данных
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
// Сначала удаляем старую запись из базы
МенеджерЗаписи.Удалить();
// Теперь меняем значение измерения на новое
МенеджерЗаписи.Назначение = ТекНазначение;
// Записываем новую запись
МенеджерЗаписи.Записать();
КонецЕсли;
Важный нюанс: при использовании этого метода нужно быть уверенным, что запись с новым значением измерения еще не существует в базе, иначе метод Записать() вызовет ошибку уникальности. Если такая вероятность есть, следует предусмотреть проверку или обработку исключения.
Если записей много или мы хотим работать через РегистрСведенийНаборЗаписей, мы можем установить отбор не по всем измерениям, а только по тем, которые не меняются. Выясним, как это работает: если мы не устанавливаем отбор по полю Назначение, платформа позволит нам менять его внутри набора, так как формально записи будут соответствовать фильтру (например, только по полю Номенклатура).
Посмотрим на пример кода:
НЗ = РегистрыСведений.НазначениеНоменклатуры.СоздатьНаборЗаписей();
// Устанавливаем отбор только по тем полям, которые НЕ будут меняться
НЗ.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
// Читаем все записи по данной номенклатуре
НЗ.Прочитать();
Для Каждого Запись Из НЗ Цикл
// Ищем конкретную запись, которую нужно изменить
Если Запись.Назначение = Выборка.Назначение Тогда
Запись.Назначение = ТекНазначение;
КонецЕсли;
КонецЦикла;
// Записываем набор. Теперь ошибки не будет, так как отбор по "Назначению" не установлен
НЗ.Записать();
Проанализируем этот подход: он эффективен, если по данной номенклатуре в регистре немного записей. Если же записей тысячи, чтение всего набора в память может быть неоптимальным с точки зрения производительности.
Для массовой коррекции данных лучше всего подходит методика выгрузки в Таблицу Значений (ТЗ). Это позволяет полностью контролировать процесс и избежать проблем с блокировками и проверками набора в процессе цикла.
Рассмотрим по шагам этот процесс:
Выгрузить().Пример реализации:
// 1. Извлекаем старые данные
НЗ_Старый = РегистрыСведений.НазначениеНоменклатуры.СоздатьНаборЗаписей();
НЗ_Старый.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
НЗ_Старый.Отбор.Назначение.Установить(Выборка.Назначение);
НЗ_Старый.Прочитать();
ТЗ_Данные = НЗ_Старый.Выгрузить();
// 2. Удаляем старые записи из базы
НЗ_Старый.Очистить();
НЗ_Старый.Записать();
// 3. Модифицируем данные в ТЗ
Для Каждого СтрокаТЗ Из ТЗ_Данные Цикл
СтрокаТЗ.Назначение = ТекНазначение;
КонецЦикла;
// 4. Записываем новые данные
НЗ_Новый = РегистрыСведений.НазначениеНоменклатуры.СоздатьНаборЗаписей();
НЗ_Новый.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
НЗ_Новый.Отбор.Назначение.Установить(ТекНазначение);
НЗ_Новый.Загрузить(ТЗ_Данные);
НЗ_Новый.Записать();
В периодических регистрах сведений измерение Период также является частью ключа. Если ваша задача — изменить дату записи, применяются те же правила. Вы не можете просто поменять Запись.Период внутри набора, у которого установлен отбор по периоду. Вам придется либо использовать МенеджерЗаписи с удалением и созданием, либо манипулировать набором без отбора по дате.
Также стоит помнить о свойстве ОбменДанными.Загрузка = Истина. При массовом изменении записей установка этого свойства у набора записей позволит пропустить лишние проверки в модулях набора и подписках на события, что значительно ускорит обработку больших массивов информации.
Подведем итоги нашего анализа. Платформа 1С запрещает менять поля, входящие в Отбор набора записей, для поддержания ссылочной целостности и корректности индексов. Для решения задачи выберите подходящий инструмент:
Всегда помните, что изменение измерения — это фактически создание новой сущности в базе данных. Проверяйте наличие «дублей» перед записью, чтобы избежать аварийного завершения работы вашего алгоритма.