Как обеспечить уникальность и избежать дублей при записи в регистр сведений в 1С?

Программист 1С v8.3 (Управляемые формы)
← На главную

Работа с Регистрами Сведений (РС) — это одна из наиболее частых задач разработчика 1С. Однако при записи данных программным способом часто возникают вопросы: почему система перезаписывает данные без предупреждения? Как заставить программу выдать ошибку, если пользователь ввел дублирующие комбинации измерений? Почему в регистре остается только одна запись, хотя в документе их было пять?

В этой статье мы подробно разберем механизмы работы НабораЗаписей, проанализируем типичные ошибки при программной записи и выясним, как правильно организовать контроль уникальности данных.

Понимание механизма уникальности в 1С

Прежде чем переходить к коду, давайте вспомним основы. Уникальность записи в регистре сведений определяется комбинацией значений его измерений (и периода, если регистр периодический). Платформа 1С на уровне базы данных создает уникальный индекс по этим полям. Это означает, что физически в таблице не может существовать двух строк, у которых набор измерений полностью совпадает.

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

Решение 1: Использование метода Записать(Ложь)

Если ваша задача — запретить перезапись существующих данных и получить системное сообщение об ошибке, следует использовать запись без замещения. Проанализируем, как это работает на практике.

Когда мы вызываем НаборЗаписей.Записать(Ложь), система проверяет, нет ли в базе данных записей, ключевые поля которых (измерения) совпадают с записями в нашем наборе. Если совпадение найдено, генерируется исключение.

Важный нюанс: при использовании Записать(Ложь) вы должны быть уверены, что в наборе нет записей, которые уже физически присутствуют в базе под текущим отбором, иначе транзакция будет отменена. Рассмотрим пример безопасной записи:


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

Решение 2: Предварительная проверка дублей в источнике (ТЧ документа)

Часто проблема кроется не в самом регистре, а в том, что пользователь ввел одинаковые строки в табличную часть (ТЧ) документа. Если мы просто обходим ТЧ циклом и записываем каждую строку в регистр по отдельности, мы получим «последнюю выжившую» запись.

Рассмотрим алгоритм проверки данных перед проведением документа. Мы можем использовать объект ТаблицаЗначений и метод Свернуть(), чтобы быстро найти дубли:


// Выгружаем ТЧ во временную таблицу
ТЗ_Проверки = Объект.Состав.Выгрузить();

// Запоминаем количество строк до сворачивания
КоличествоДо = ТЗ_Проверки.Количество();

// Сворачиваем по измерениям нашего регистра
ТЗ_Проверки.Свернуть("Сотрудник, Номенклатура, Склад", "Количество");

// Если строк стало меньше - значит были дубли
Если ТЗ_Проверки.Количество() < КоличествоДо Тогда
    ТекстОшибки = "В документе присутствуют строки с одинаковыми измерениями! Запись в регистр невозможна.";
    ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстОшибки);
    Отказ = Истина;
КонецЕсли;

Этот способ является наиболее «дружелюбным» к пользователю, так как он получает понятное предупреждение еще до попытки обращения к базе данных.

Решение 3: Правильное использование Набора Записей

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

При этом подходе мы создаем один набор записей, заполняем его всеми строками из документа и записываем одним движением. Это гарантирует, что если внутри документа есть дубли, 1С сама выдаст ошибку уникальности при методе Записать().


НаборЗаписей = РегистрыСведений.МойРегистр.СоздатьНаборЗаписей();

// Если мы хотим перезаписать данные только по конкретному документу (если ссылка на него есть в измерениях)
НаборЗаписей.Отбор.ДокументОснование.Установить(Объект.Ссылка);

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

// Система проверит уникальность внутри набора и в базе данных автоматически
НаборЗаписей.Записать(Истина); 

Почему это лучше? Использование Записать(Истина) с установленным отбором по ключевому измерению (например, по ссылке на текущий документ) позволяет легко обновлять данные при перепроведении документа. Старые записи, связанные с этим документом, удаляются, а новые записываются.

Решение 4: Менеджер записи для точечной работы

Если вам нужно проверить наличие конкретной записи перед её созданием, рассмотрите использование РегистрСведенийМенеджерЗаписи. Этот объект предназначен для работы с одной конкретной строкой регистра.


МенеджерЗаписи = РегистрыСведений.МойРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Измерение1 = Значение1;
МенеджерЗаписи.Измерение2 = Значение2;

МенеджерЗаписи.Прочитать();

Если МенеджерЗаписи.Выбран() Тогда
    // Запись уже существует. Можем либо выдать ошибку, либо обновить ресурсы.
    МенеджерЗаписи.Ресурс1 = НовоеЗначение;
    МенеджерЗаписи.Записать();
Иначе
    // Записи нет, создаем новую
    МенеджерЗаписи.Ресурс1 = НовоеЗначение;
    МенеджерЗаписи.Записать();
КонецЕсли;

Архитектурный совет: Подчинение регистратору

Если логика вашего приложения подразумевает, что данные в регистр попадают только из конкретного документа, самым правильным решением будет установка режима записи «Подчинение регистратору».

В этом случае:

  1. Платформа берет на себя весь контроль уникальности в рамках регистратора.
  2. При отмене проведения документа записи в регистре удаляются автоматически.
  3. Вам не нужно вручную прописывать код очистки наборов записей.
  4. Уникальность будет проверяться по комбинации: Регистратор + НомерСтроки (если не задано иное) или по измерениям в рамках одного регистратора.

Рассмотрим ситуацию: если вы пытаетесь записать в подчиненный регистр две строки с одинаковыми измерениями из одного документа, 1С позволит это сделать (так как записи будут различаться номером строки), но это может противоречить бизнес-логике. В таком случае проверка из Решения 2 остается обязательной.

Выясним причину отсутствия ошибок

Почему у автора темы не возникало ошибки при записи дублей? Проанализируем: если вы используете НаборЗаписей.Записать(Истина) и в цикле по ТЧ документа на каждой итерации создаете новый объект набора с отбором по измерениям, то каждая следующая итерация просто находит в базе старую запись (созданную на предыдущем шаге), удаляет её (так как Замещать = Истина) и пишет новую. В итоге в базе остается только результат последней итерации. Ошибки не будет, потому что формально вы каждый раз «обновляете» запись, а не пытаетесь создать вторую такую же одновременно.

Резюме: Чтобы система «ругалась» на дубли, их нужно подавать в Записать() одновременно в составе одного набора, либо использовать Записать(Ложь) для проверки наличия записи в БД.

← На главную