В процессе разработки в системе 1С:Предприятие часто возникает задача логирования или хранения истории изменений. Программисты сталкиваются с тем, что при попытке записать «такие же» данные в регистр сведений, старые записи бесследно исчезают, а новые занимают их место. Это происходит из-за фундаментального принципа работы регистра сведений: уникальность записи определяется набором её измерений. Если мы записываем строку, где все измерения (и период для периодических регистров) совпадают с уже существующей, система просто обновляет старую запись.
В данной статье мы подробно разберем, как обойти это ограничение, проанализируем различные подходы и выясним, какой из них лучше всего подходит для ваших задач. Мы научимся создавать архитектуру регистра так, чтобы данные «накапливались», а не перезаписывались.
Прежде чем переходить к коду, давайте проанализируем ситуацию на уровне базы данных. Регистр сведений — это таблица, первичный ключ которой состоит из всех его Измерений. В периодических регистрах к этому ключу добавляется поле Период. Если вы пытаетесь выполнить запись с тем же первичным ключом, СУБД выполняет операцию обновления (UPDATE) или предварительного удаления.
Многие начинающие разработчики совершают ошибку, полагая, что метод Записать(Ложь) у набора записей позволит «дозаписать» дубликаты. На самом деле параметр Замещать = Ложь лишь указывает системе не очищать записи, которые не входят в текущий установленный отбор. Если же внутри вашего набора или в базе уже есть строка с такими же измерениями, она будет перезаписана в любом случае.
Один из самых простых и производительных способов — добавить в структуру регистра дополнительное измерение, например, НомерЗаписи или Индекс с типом Число. Это позволит нам искусственно разделять записи, сделанные в один и тот же момент времени.
Рассмотрим пример кода, где при каждом проведении документа мы увеличиваем счетчик для записи новых данных:
// Предположим, мы записываем данные в независимый регистр
Менеджер = РегистрыСведений.МояИсторияИзменений.СоздатьМенеджерЗаписи();
Менеджер.Объект = СсылкаНаОбъект;
Менеджер.ДатаСобытия = ТекущаяДата();
// Находим максимальный индекс для текущего объекта и даты
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МАКСИМУМ(МояИсторияИзменений.Индекс) КАК МаксИндекс
|ИЗ
| РегистрСведений.МояИсторияИзменений КАК МояИсторияИзменений
|ГДЕ
| МояИсторияИзменений.Объект = &Объект";
Запрос.УстановитьПараметр("Объект", СсылкаНаОбъект);
Выборка = Запрос.Выполнить().Выбрать();
НовыйИндекс = 1;
Если Выборка.Следующий() Тогда
НовыйИндекс = Выборка.МаксИндекс + 1;
КонецЕсли;
Менеджер.Индекс = НовыйИндекс;
Менеджер.Данные = "Новое значение";
Менеджер.Записать();
Этот метод гарантирует уникальность каждой новой записи, однако требует дополнительного запроса к базе данных перед каждой записью, что может сказаться на производительности при массовых операциях.
Если нам не важен порядковый номер записи, а нужно просто гарантировать, что записи никогда не совпадут, мы можем добавить измерение ИдентификаторЗаписи с типом УникальныйИдентификатор. Это самый надежный способ «размножить» строки в регистре.
Проанализируем ситуацию: при каждой записи мы будем генерировать новый GUID. Таким образом, даже если все остальные поля (объект, дата, сумма, номенклатура) идентичны, первичный ключ таблицы всегда будет уникальным.
НаборЗаписей = РегистрыСведений.ЛогСобытий.СоздатьНаборЗаписей();
НоваяСтрока = НаборЗаписей.Добавить();
НоваяСтрока.Период = ТекущаяДата();
НоваяСтрока.Событие = "Перепроведение документа";
// Генерируем уникальный ключ для этой конкретной записи
НоваяСтрока.ИдентификаторЗаписи = Новый УникальныйИдентификатор();
// Важно: записываем с параметром Замещать = Ложь,
// чтобы не затереть записи по другим событиям, если не установлен отбор
НаборЗаписей.Записать(Ложь);
Важный нюанс: использование GUID увеличивает размер индексов таблицы. Если записей планируется миллионы, база данных будет расти быстрее, а поиск по такому регистру может замедлиться.
Если ваша задача — записывать состояние документа при каждом его изменении, наиболее правильным с точки зрения архитектуры 1С будет использование встроенного механизма версионирования или его имитация. В платформе у каждого объекта есть свойство ВерсияДанных.
Мы можем добавить в регистр сведений измерение ВерсияОбъекта (тип Строка или Число). При записи данных из документа мы просто передаем туда текущую версию объекта. Поскольку при каждом изменении объекта его версия обновляется, записи в регистре будут уникальными относительно каждого состояния документа.
Посмотрим на пример в модуле объекта документа:
Процедура ПриЗаписи(Отказ)
МенеджерЗаписи = РегистрыСведений.ИсторияДокументов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Документ = ЭтотОбъект.Ссылка;
МенеджерЗаписи.Версия = ЭтотОбъект.ВерсияДанных; // Используем штатную версию
МенеджерЗаписи.ДатаИзменения = ТекущаяДата();
МенеджерЗаписи.Данные = "Любая информация";
МенеджерЗаписи.Записать();
КонецПроцедуры
Часто программисты пытаются сделать регистр периодическим в надежде, что это решит проблему. Однако стандартная минимальная периодичность в 1С — это 1 секунда. Если код выполняется быстро (например, в цикле или при групповой обработке), несколько записей могут попасть в одну и ту же секунду. В этом случае последняя запись просто «съест» предыдущие.
Выясним, как это обойти:
Миллисекунды (тип Число), куда записывать значение через ТекущаяУниверсальнаяДатаВМиллисекундах().НомерСтроки.Рассмотрим ситуацию, описанную на форуме: автор жалуется, что данные все равно затираются, хотя он использует независимый регистр. Разберем причину:
Если вы создаете НаборЗаписей, устанавливаете отбор по измерениям и вызываете Записать(), то 1С сначала удаляет все старые записи, соответствующие этому отбору, а затем записывает новые из набора. Если в наборе только одна строка, то в базе останется только одна строка.
Чтобы добавлять записи без удаления существующих по тому же фильтру, нужно либо:
Набор.Прочитать(), добавить в него новую строку и записать весь обновленный массив строк.МенеджерЗаписи, который работает с одной строкой, но помните — он всё равно не создаст дубль, если измерения совпадут! Он просто обновит существующую запись.Иногда стоит проанализировать, а правильно ли выбран тип объекта. Если ваша цель — просто фиксировать поток однотипных событий (логировать), то Регистр накопления (вид «Обороты») подходит для этого гораздо лучше.
В регистрах накопления нет проверки уникальности по измерениям в том смысле, как в регистрах сведений. Каждая запись в таблицу движений — это отдельная физическая строка. Если вы запишете 10 абсолютно одинаковых строк в регистр накопления, в базе появится 10 строк. Это штатное поведение системы, которое избавляет вас от необходимости генерировать ГУИДы или счетчики.
Для реализации повторной записи одинаковых данных в регистр сведений мы рекомендуем:
Периода, так как точности в 1 секунду часто недостаточно.Выбирайте метод исходя из объемов данных: для небольших логов подойдет ГУИД, для высоконагруженных систем лучше рассмотреть вариант с подчиненным регистром или регистром накопления.