Решение задач 1С:Предприятие 8 на примерах

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

АлгоритмыОпубликовано

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

Задача: записать, изменить и удалить данные регистра сведений из кода

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

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

Какой бывает регистр сведений

От настроек регистра зависит, каким способом в него можно писать:

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

Ключ записи — это совокупность измерений, а для периодического регистра ещё и периода (для подчинённого — регистратора и номера строки). Двух записей с одинаковым ключом быть не может, отсюда большинство ошибок при записи.

Менеджер записи: одна запись по ключу

Менеджер записи удобен, когда нужно создать, изменить или удалить ровно одну запись независимого регистра. Его получают методом СоздатьМенеджерЗаписи().

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

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

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

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

Чтобы изменить только часть ресурсов, существующую запись сначала читают: заполняют ключевые поля, вызывают Прочитать() и проверяют Выбран().

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

Если МенеджерЗаписи.Выбран() Тогда
    МенеджерЗаписи.Значение = Склад;
    МенеджерЗаписи.Записать();
КонецЕсли;

Набор записей регистра сведений

Набор записей — основной инструмент для массовой записи. Он работает с группой записей, отобранных по значениям измерений, и при записи по умолчанию замещает все записи, попадающие под отбор. Это и удобно, и опасно: без отбора набор относится ко всему регистру.

&НаСервере
Процедура ЗаписатьЦеныНаДату(ВидЦены, ДатаЦены, ТаблицаЦен)

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

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

    НаборЗаписей.Записать(); // заменит все цены этого вида на эту дату

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

Отбор в наборе задаёт, какие записи будут заменены. В примере набор перепишет все цены выбранного вида на указанную дату: позиции, которых нет в ТаблицаЦен, удалятся. Если нужно лишь добавить или обновить конкретные позиции, установите отбор ещё и по Номенклатура — и записывайте каждую позицию своим набором, либо используйте менеджер записи.

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

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

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

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

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

Запись в регистр, подчинённый регистратору

Для подчинённого регистра набор всегда работает с отбором по регистратору. В модуле документа это делают движения: Движения.ИмяРегистра уже содержит набор с нужным отбором. Вне документа отбор ставят явно.

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

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

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

Удалить запись регистра сведений программно

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

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

Группу записей удаляют пустым набором: устанавливают отбор и записывают набор, ничего в него не добавляя. Все записи под отбором будут удалены.

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

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

Очистить регистр сведений полностью

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

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

На больших регистрах удаление одной транзакцией создаёт длительную блокировку и нагружает СУБД. Безопаснее очищать порциями — например, по значениям первого измерения или по периодам: получить запросом список значений и записать пустой набор с отбором по каждому из них. Подчинённый регистр так очистить нельзя: его записи удаляют по регистраторам или перепроведением документов.

Чтение: срез последних и методы менеджера

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

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

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

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

Ресурсы = РегистрыСведений.НастройкиПользователей.Получить(
    Новый Структура("Пользователь, Настройка", Пользователь, "ОсновнойСклад"));
Склад = Ресурсы.Значение;

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

Ошибка «Записи регистра сведений стали неуникальными»

Ошибка означает, что после записи в регистре оказались бы две записи с одинаковым ключом. Типичные причины:

  • Дубли внутри набора. В набор дважды добавлена одна и та же номенклатура на один период. Перед записью сверните исходную таблицу по измерениям или проверяйте наличие строки.
  • Периодичность регистра. Для регистра с периодичностью «День» период приводится к началу дня, поэтому две записи на 10:00 и 15:00 одного дня совпадают по ключу.
  • Запись с Замещать = Ложь. Записать(Ложь) добавляет записи к существующим, и любой совпавший ключ вызывает ошибку.

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

Блокировки при записи из нескольких сеансов

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

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

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

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

Менеджер записи или набор записей

КритерийМенеджер записиНабор записей
Количество записейОдна по полному ключуЛюбое под отбором
Подчинённый регистрНе используетсяДа, с отбором по регистратору
УдалениеУдалить()Пустой набор с отбором
Массовая загрузкаМедленноОптимально
РискПерезапись одной записиУдаление всего, что под отбором

Актуальность для новых версий

Менеджер записи, набор записей и методы Получить(), ПолучитьПоследнее() работают одинаково во всех версиях платформы 8.3, включая 8.3.2x. Код выполняется только на сервере. В конфигурациях с управляемыми блокировками (режим «Управляемый» или «Автоматический и управляемый») при параллельной работе блокировку нужно устанавливать явно через БлокировкаДанных, как в примере.

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

Чем Записать(Истина) отличается от Записать(Ложь)?

С Истина набор заменяет все записи, попадающие под отбор, а менеджер перезаписывает запись с тем же ключом. С Ложь записи добавляются к существующим, и совпадение ключа приводит к ошибке.

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

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

Как полностью очистить независимый регистр сведений?

Создайте набор записей без отбора и вызовите Записать(). На больших регистрах лучше очищать порциями — по значениям измерения или периодам.

Откуда ошибка «Записи регистра сведений стали неуникальными»?

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

Расширение конфигурации 1С: создание, установка и перехват методов

Алгоритмы

Практическое руководство по расширениям конфигурации 1С: как создать и подключить расширение, перехватывать типовые методы аннотациями, безопасно обновлять конфигурацию и отключать расширение.

Таблица значений в 1С: создание, поиск строк, передача в запрос и на форму

Алгоритмы

Практическое руководство по таблице значений в 1С: как создать её программно, добавлять и искать строки, сворачивать данные, превращать массив в таблицу и передавать таблицу в запрос или на форму.

Хеширование HMAC-SHA1 и HMAC-SHA256 средствами 1С

Алгоритмы

Готовая функция на языке 1С, которая считает HMAC-SHA1 и HMAC-SHA256 и возвращает результат в Hex и Base64. Пригодится для подписи запросов к сторонним API без внешних компонент.

XML в 1С программно: запись, чтение, DOM и выгрузка данных

Интеграция и веб-сервисы

Готовые примеры работы с XML на встроенном языке 1С: запись и чтение, DOM с XPath, преобразование XML в структуру, выгрузка справочника и разбор ошибок парсинга.