Как записать повторяющиеся данные в регистр сведений 1С и избежать их затирания

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

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

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

Понимание уникальности: почему данные затираются?

Прежде чем переходить к коду, давайте проанализируем ситуацию на уровне базы данных. Регистр сведений — это таблица, первичный ключ которой состоит из всех его Измерений. В периодических регистрах к этому ключу добавляется поле Период. Если вы пытаетесь выполнить запись с тем же первичным ключом, СУБД выполняет операцию обновления (UPDATE) или предварительного удаления.

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

Решение 1: Использование дополнительного измерения-счетчика

Один из самых простых и производительных способов — добавить в структуру регистра дополнительное измерение, например, НомерЗаписи или Индекс с типом Число. Это позволит нам искусственно разделять записи, сделанные в один и тот же момент времени.

Рассмотрим пример кода, где при каждом проведении документа мы увеличиваем счетчик для записи новых данных:


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

// Находим максимальный индекс для текущего объекта и даты
Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	МАКСИМУМ(МояИсторияИзменений.Индекс) КАК МаксИндекс
|ИЗ
|	РегистрСведений.МояИсторияИзменений КАК МояИсторияИзменений
|ГДЕ
|	МояИсторияИзменений.Объект = &Объект";

Запрос.УстановитьПараметр("Объект", СсылкаНаОбъект);
Выборка = Запрос.Выполнить().Выбрать();

НовыйИндекс = 1;
Если Выборка.Следующий() Тогда
    НовыйИндекс = Выборка.МаксИндекс + 1;
КонецЕсли;

Менеджер.Индекс = НовыйИндекс;
Менеджер.Данные = "Новое значение";
Менеджер.Записать();

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

Решение 2: Добавление измерения с типом УникальныйИдентификатор (GUID)

Если нам не важен порядковый номер записи, а нужно просто гарантировать, что записи никогда не совпадут, мы можем добавить измерение ИдентификаторЗаписи с типом УникальныйИдентификатор. Это самый надежный способ «размножить» строки в регистре.

Проанализируем ситуацию: при каждой записи мы будем генерировать новый GUID. Таким образом, даже если все остальные поля (объект, дата, сумма, номенклатура) идентичны, первичный ключ таблицы всегда будет уникальным.


НаборЗаписей = РегистрыСведений.ЛогСобытий.СоздатьНаборЗаписей();
НоваяСтрока = НаборЗаписей.Добавить();
НоваяСтрока.Период = ТекущаяДата();
НоваяСтрока.Событие = "Перепроведение документа";
// Генерируем уникальный ключ для этой конкретной записи
НоваяСтрока.ИдентификаторЗаписи = Новый УникальныйИдентификатор();

// Важно: записываем с параметром Замещать = Ложь, 
// чтобы не затереть записи по другим событиям, если не установлен отбор
НаборЗаписей.Записать(Ложь);

Важный нюанс: использование GUID увеличивает размер индексов таблицы. Если записей планируется миллионы, база данных будет расти быстрее, а поиск по такому регистру может замедлиться.

Решение 3: Работа с версионированием данных

Если ваша задача — записывать состояние документа при каждом его изменении, наиболее правильным с точки зрения архитектуры 1С будет использование встроенного механизма версионирования или его имитация. В платформе у каждого объекта есть свойство ВерсияДанных.

Мы можем добавить в регистр сведений измерение ВерсияОбъекта (тип Строка или Число). При записи данных из документа мы просто передаем туда текущую версию объекта. Поскольку при каждом изменении объекта его версия обновляется, записи в регистре будут уникальными относительно каждого состояния документа.

Посмотрим на пример в модуле объекта документа:


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

Решение 4: Проблема периодичности в 1 секунду

Часто программисты пытаются сделать регистр периодическим в надежде, что это решит проблему. Однако стандартная минимальная периодичность в 1С — это 1 секунда. Если код выполняется быстро (например, в цикле или при групповой обработке), несколько записей могут попасть в одну и ту же секунду. В этом случае последняя запись просто «съест» предыдущие.

Выясним, как это обойти:

  1. Добавить измерение Миллисекунды (тип Число), куда записывать значение через ТекущаяУниверсальнаяДатаВМиллисекундах().
  2. Использовать подчинение регистратору. Если регистр сведений является подчиненным документу, то внутри одного набора записей (движений документа) система автоматически различает строки по полю НомерСтроки.

Типичные ошибки при использовании Набора Записей

Рассмотрим ситуацию, описанную на форуме: автор жалуется, что данные все равно затираются, хотя он использует независимый регистр. Разберем причину:

Если вы создаете НаборЗаписей, устанавливаете отбор по измерениям и вызываете Записать(), то 1С сначала удаляет все старые записи, соответствующие этому отбору, а затем записывает новые из набора. Если в наборе только одна строка, то в базе останется только одна строка.

Чтобы добавлять записи без удаления существующих по тому же фильтру, нужно либо:

  1. Прочитать существующий набор методом Набор.Прочитать(), добавить в него новую строку и записать весь обновленный массив строк.
  2. Использовать МенеджерЗаписи, который работает с одной строкой, но помните — он всё равно не создаст дубль, если измерения совпадут! Он просто обновит существующую запись.

Альтернативный подход: Регистр накопления

Иногда стоит проанализировать, а правильно ли выбран тип объекта. Если ваша цель — просто фиксировать поток однотипных событий (логировать), то Регистр накопления (вид «Обороты») подходит для этого гораздо лучше.

В регистрах накопления нет проверки уникальности по измерениям в том смысле, как в регистрах сведений. Каждая запись в таблицу движений — это отдельная физическая строка. Если вы запишете 10 абсолютно одинаковых строк в регистр накопления, в базе появится 10 строк. Это штатное поведение системы, которое избавляет вас от необходимости генерировать ГУИДы или счетчики.

Резюме

Для реализации повторной записи одинаковых данных в регистр сведений мы рекомендуем:

Выбирайте метод исходя из объемов данных: для небольших логов подойдет ГУИД, для высоконагруженных систем лучше рассмотреть вариант с подчиненным регистром или регистром накопления.

← На главную