Запись в регистр сведений без регистратора: менеджер, набор, форма

· 9 мин

Содержание

Одну запись в независимый регистр сведений 1С добавляют через менеджер записи: РегистрыСведений.Имя.СоздатьМенеджерЗаписи(), затем заполняют период, измерения и ресурсы и вызывают Записать(). Много записей сразу — через набор записей СоздатьНаборЗаписей() с отбором. Без кода — из формы списка регистра кнопкой «Создать».

Частое заблуждение: «Записать() всегда добавляет новую строку». Нет. По умолчанию запись с тем же ключом молча заменяется. А набор без отбора с параметром замещения стирает весь регистр. Всё, что ниже, — про то, как добавить запись и не потерять чужие данные. Механика общая для платформы 8.3 и не зависит от конфигурации.

Как независимый регистр сведений хранит записи

У регистра сведений два режима записи: «Подчинение регистратору» и «Независимый». В первом случае записи создаёт документ при проведении. Во втором — кто угодно: пользователь в форме, обработка, регламентное задание, обмен.

Каждая запись независимого регистра однозначно определяется ключом:

  • 📅 Период — только если регистр периодический;
  • 🔑 все измерения — в порядке, заданном в конфигураторе.

Ресурсы и реквизиты в ключ не входят. Две записи с одинаковым периодом и одинаковыми измерениями существовать не могут — это и есть уникальность на уровне платформы.

Периодичность и что она делает с датой

Периодичность задаётся в свойствах регистра: «в пределах секунды», «дня», «месяца», «квартала», «года» или «по позиции регистратора» (последняя — только для подчинённых регистров). При записи платформа приводит период к началу интервала.

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

Перед загрузкой проверьте периодичность регистра. Две цены на 05.03 и 20.03 в регистре «в пределах месяца» превратятся в одну — последнюю записанную.

Свойства измерений, которые влияют на запись

Свойство измеренияЧто делает при добавлении записи
Запрет незаполненных значенийПустое значение измерения — ошибка записи
Основной отборИзмерение доступно в отборе набора и используется в механизмах обмена
ВедущееЗапись удаляется при удалении объекта, на который она ссылается
ТипЗначение другого типа не запишется или будет приведено

Подготовка: права, контекст исполнения и менеджер записи

До кода проверьте три вещи.

  1. Права. У роли пользователя должно быть право «Изменение» (для интерактивного ввода — ещё «Интерактивное добавление») на регистр. Без них будет исключение о нарушении прав доступа.
  2. Контекст. Менеджеры и наборы записей живут только на сервере. В модуле формы код пишут в процедуре с директивой &НаСервере или в общем серверном модуле. На клиенте РегистрыСведений недоступен.
  3. Режим записи регистра. В подчинённый регистр менеджер записи вы не создадите — у него есть только набор записей с отбором по регистратору. Если СоздатьМенеджерЗаписи() не находится, регистр не независимый.

Добавить запись вручную, без программирования

Если нужна одна-две записи, код не нужен:

  1. Включите команду «Все функции»: главное меню (☰) → Сервис → Параметры → флажок «Отображать команду “Все функции”».
  2. Главное меню → Все функции → ветка Регистры сведений → нужный регистр.
  3. В форме списка нажмите «Создать» (или Ins).
  4. Заполните период, измерения, ресурсы и нажмите «Записать и закрыть».

Форма записи работает через тот же менеджер записи. Если запись с таким ключом уже есть, форма откажет и сообщит о дубле — в отличие от кода, где по умолчанию стоит замещение.

Создание менеджера записи

Менеджер записи — объект для работы с одной записью регистра. Создаётся одной строкой:

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

Здесь и дальше ПлановыеЦены — пример регистра: периодический «в пределах дня», измерения Номенклатура и Склад, ресурс Цена, реквизит Комментарий. Замените имена на свои.

Заполнение периода, измерений и ресурсов записи

Поля менеджера записи называются так же, как реквизиты регистра в конфигураторе. Полный пример добавления записи в независимый регистр сведений:

&НаСервере
Процедура ДобавитьЦенуНаСервере(Номенклатура, Склад, Цена)

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

    // Ключ записи
    МенеджерЗаписи.Период       = НачалоДня(ТекущаяДатаСеанса());
    МенеджерЗаписи.Номенклатура = Номенклатура;
    МенеджерЗаписи.Склад        = Склад;

    // Ресурсы и реквизиты
    МенеджерЗаписи.Цена         = Цена;
    МенеджерЗаписи.Комментарий  = "Загружено обработкой";

    МенеджерЗаписи.Записать(); // Замещать = Истина по умолчанию

КонецПроцедуры

Что важно при заполнении:

  • 🕐 Период берите от ТекущаяДатаСеанса(), а не от ТекущаяДата(). Первая учитывает часовой пояс сеанса, вторая берёт время компьютера, где выполняется код.
  • 🧩 Незаполненное измерение — допустимая часть ключа. Запись «Номенклатура = Х, Склад = пусто» — отдельная запись, а не «цена для всех складов». Если такое не нужно, включите «Запрет незаполненных значений».
  • 🔢 Тип ресурса. Число с длиной 15 и точностью 2 обрежет 10,555 до 10,56 по правилам округления. Проверьте точность заранее.
  • 📝 Реквизиты заполняются как ресурсы, но в отборах среза последних не участвуют.

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

Методы записи в регистр: менеджер или набор записей

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

СпособКогдаРиск
МенеджерЗаписи.Записать(Истина)Одна запись, перезапись допустимаТихо затрёт запись с тем же ключом
МенеджерЗаписи.Записать(Ложь)Одна запись, дубль недопустимИсключение, если ключ уже есть
НаборЗаписей.Записать(Истина) с отборомПакет записей с заменой «среза»Удалит всё, что попало в отбор и нет в наборе
НаборЗаписей.Записать(Ложь)Пакет только новых записейВесь набор не запишется при первом дубле

Набор записей с отбором

Набор записей — коллекция записей, ограниченная отбором. При Записать(Истина) платформа удаляет из базы всё, что соответствует отбору, и пишет содержимое набора.

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

НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период       = НачалоДня(ТекущаяДатаСеанса());
НоваяЗапись.Номенклатура = Номенклатура;
НоваяЗапись.Склад        = Склад;
НоваяЗапись.Цена         = Цена;

НаборЗаписей.Записать(); // Замещение = Истина

Значения полей каждой записи должны совпадать с отбором. Отбор не заполняет их сам: если оставить Склад пустым при отборе по складу, платформа откажет в записи.

Набор без отбора и Записать(Истина) удаляет ВСЕ записи регистра и оставляет только то, что лежит в наборе. Так теряют справочные данные целиком. Перед первым запуском такой обработки сделайте резервную копию базы и проверьте её на копии.

Как дописать запись, ничего не удаляя

Если нужно именно добавить, а существующие записи не трогать, используйте Записать(Ложь) — у менеджера или у набора. При конфликте ключа вы получите исключение, а не тихую замену. Это честнее: ошибку видно, данные целы.

Удаление и изменение

Для полноты: тот же менеджер записи умеет читать и удалять. Заполните ключ, вызовите Прочитать(), проверьте Выбран(), затем измените ресурс и Записать() или вызовите Удалить(). Набор удаляет записи так: установили отбор, ничего не добавили, Записать().

Ошибки при записи и перехват исключений

Запись в регистр — операция с базой, она может упасть. Оборачивайте её в Попытка … Исключение и пишите причину в журнал регистрации, а не только пользователю на экран.

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

Если внутри одной операции пишете в несколько регистров, оборачивайте всё в транзакцию: НачатьТранзакцию(), затем ЗафиксироватьТранзакцию() в конце блока Попытка и ОтменитьТранзакцию() в Исключение. Тогда либо записалось всё, либо ничего.

Диагностика: выберите симптом

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

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

Есть ли уже запись с таким ключом

Самый надёжный способ — запрос по точному ключу:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
|    ПлановыеЦены.Цена
|ИЗ
|    РегистрСведений.ПлановыеЦены КАК ПлановыеЦены
|ГДЕ
|    ПлановыеЦены.Период = &Период
|    И ПлановыеЦены.Номенклатура = &Номенклатура
|    И ПлановыеЦены.Склад = &Склад";
Запрос.УстановитьПараметр("Период", НачалоДня(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
Запрос.УстановитьПараметр("Склад", Склад);

ЗаписьЕсть = Не Запрос.Выполнить().Пустой();

Изменилось ли значение на дату

Для периодического регистра сравнивают новое значение с последним действующим. Короткий путь — метод менеджера регистра ПолучитьПоследнее():

Отбор = Новый Структура("Номенклатура, Склад", Номенклатура, Склад);
Последнее = РегистрыСведений.ПлановыеЦены.ПолучитьПоследнее(ТекущаяДатаСеанса(), Отбор);

Если Последнее.Цена <> Цена Тогда
    // пишем новую запись
КонецЕсли;

Метод возвращает структуру ресурсов. Если записей нет, ресурсы будут пустыми (для числа — 0). Для пакетной проверки сотен позиций используйте в запросе виртуальную таблицу СрезПоследних(&Дата, Склад = &Склад) — один запрос вместо сотни обращений.

Чтение и запись в одной транзакции

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

НачатьТранзакцию();
Попытка
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ПлановыеЦены");
    ЭлементБлокировки.УстановитьЗначение("Номенклатура", Номенклатура);
    ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    Блокировка.Заблокировать();

    // проверка запросом и запись

    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Массовая загрузка записей в регистр сведений без тормозов

Загрузка 50 000 цен через 50 000 менеджеров записи — это 50 000 отдельных обращений к базе. Набор записей пишет пачку за одно обращение и работает заметно быстрее.

Приёмы, которые дают основной выигрыш:

  1. Один набор на срез. Сгруппируйте данные по периоду. Для каждой даты — набор с отбором только по Период, заполненный всеми строками этой даты, и Записать(Истина). Срез за дату заменится целиком, остальные даты не пострадают.
  2. Загрузить() вместо цикла. Если данные уже в таблице значений с колонками, названными как поля регистра, вызовите НаборЗаписей.Загрузить(Таблица).
  3. Убрать дубли до записи. Две строки с одним ключом внутри набора дадут ошибку всей пачки. Сверните таблицу: Таблица.Свернуть("Период, Номенклатура, Склад", "Цена") — но помните, что ресурс при этом суммируется. Если суммировать нельзя, удалите дубли отдельно.
  4. Режим загрузки. НаборЗаписей.ОбменДанными.Загрузка = Истина отключает обработчики ПередЗаписью и ПриЗаписи модуля набора. Используйте, только если уверены в чистоте данных: проверки из этих обработчиков тоже не выполнятся.
  5. Пачки транзакций. Не пишите всё в одной транзакции на 50 000 строк — она долго держит блокировки. Фиксируйте каждые 1000–5000 записей.

Пример загрузки среза за одну дату:

НаборЗаписей = РегистрыСведений.ПлановыеЦены.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(ДатаЦен);
НаборЗаписей.ОбменДанными.Загрузка = Истина;

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

НаборЗаписей.Записать(Истина);

Поле Период заполняется в цикле явно. Если в таблице есть колонка Период с другой датой, ЗаполнитьЗначенияСвойств перенесёт её, и строка не попадёт в отбор.

Перед массовой загрузкой в регистр
Выполнено: 0 / 5

Частые вопросы

Можно ли добавить запись в регистр сведений с клиента?
Нет. Менеджеры и наборы записей доступны только на сервере. Вызовите серверную процедуру формы с директивой &НаСервере или функцию общего модуля с флагом «Вызов сервера».
Чем менеджер записи отличается от набора записей?
Менеджер работает с одной записью по полному ключу. Набор — с группой записей по отбору, который может включать не все измерения.
Почему вместо 17-го числа в регистре стоит 1-е?
У регистра периодичность «в пределах месяца». Платформа приводит период к началу месяца, и записи одного месяца с одинаковыми измерениями заменяют друг друга.
Как добавить запись в непериодический регистр?
Так же, только без поля Период. Ключ записи состоит из измерений, отбор набора — тоже только по измерениям.
Сработает ли подписка на событие при загрузке с ОбменДанными.Загрузка?
Обработчики подписок вызываются, но в типовом коде они обычно проверяют этот флаг и сразу выходят. Проверьте код своих подписок.