Почему в подчиненный регистратору регистр сведений без измерений нельзя записать более одной строки

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

При проектировании метаданных в платформе «1С:Предприятие 8» разработчики иногда сталкиваются с неожиданным поведением системы при попытке записать данные в регистр сведений. Типичный сценарий: создается непериодический регистр сведений, подчиненный регистратору, в котором нет ни одного измерения, но присутствует один или несколько ресурсов. Казалось бы, каждый документ-регистратор должен без проблем записывать туда свою строку. Однако при попытке проведения второго документа система выдает ошибку нарушения уникальности данных.

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

В чем суть проблемы: логическая модель регистра сведений

Чтобы понять природу этой ошибки, нам необходимо обратиться к математической модели регистра сведений. Главное назначение любого регистра сведений в системе «1С» — это хранение состояний в разрезе определенной аналитики. Регистр сведений по своей сути представляет собой функцию:

$f(Измерения, Период) \to Ресурсы$

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

Теперь проанализируем наш проблемный объект:

  1. Регистр непериодический: это значит, что поле Период отсутствует в ключе уникальности.
  2. Измерений нет: это значит, что набор пользовательских измерений пуст.
  3. Регистр подчинен регистратору: у регистра включен режим записи Подчинение регистратору.

Здесь кроется главная ловушка для начинающего разработчика: возникает иллюзия, что системное поле Регистратор автоматически становится измерением и входит в ключ уникальности. Однако это не так.

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

Что происходит на физическом уровне в СУБД (MS SQL Server / PostgreSQL)

Рассмотрим подробнее физическую структуру таблиц СУБД, которую генерирует платформа «1С» для подчиненного регистра сведений. При создании такого регистра в базе данных создаются два основных индекса:

  1. Кластерный уникальный индекс (Первичный ключ таблицы): он строится по полям [Регистратор + НомерСтроки]. Этот индекс обеспечивает физическую уникальность строк на уровне базы данных. Он без проблем позволяет разным документам делать свои записи, а одному документу — записывать множество строк в рамках одного пакета движений.
  2. Уникальный индекс по измерениям (индекс вида _ByDims): он строится по полям измерений и периоду (если регистр периодический). Именно этот индекс отвечает за соблюдение логической целостности «1С».

Когда наш регистр непериодический и не имеет измерений, СУБД пытается построить уникальный индекс по пустому набору полей (или только по разделителям учета, если включено разделение данных). Наличие уникального индекса _ByDims физически запрещает таблице иметь более одной записи. Первый документ успешно запишет строку, но при проведении второго документа СУБД вернет ошибку нарушения уникальности (Unique Key Violation), так как «пустой» ключ уникальности уже занят первой записью.

Почему периодичность «По позиции регистратора» решает проблему лишь частично?

Если мы изменим свойство регистра Периодичность на значение По позиции регистратора, ситуация изменится:

В этом случае уникальным ключом становится связка [Период + Измерения (пусто)]. Различные документы, проведенные в разные моменты времени, теперь смогут без конфликтов записывать по одной строке в этот регистр.

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

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

Вариант решения №1: Использование независимого регистра сведений

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

Разберем по шагам, как это реализовать:

  1. Установим режим записи регистра сведений в значение Независимый.
  2. Добавим новое измерение, например, ДокументИсточник с типом ссылки на ваш документ.
  3. Добавим второе измерение для обеспечения уникальности строк внутри одного документа (например, НомерСтроки с типом Число или КлючСвязи с типом УникальныйИдентификатор).

При использовании независимого регистра мы теряем автоматическую очистку движений платформой при отмене проведения документа. Нам придется управлять этим процессом вручную через программный код в модуле документа.

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


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

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

Вариант решения №2: Добавление искусственного измерения в подчиненный регистр

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

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

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


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

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

Вариант решения №3: Использование регистра накопления (Обороты)

В некоторых случаях сама попытка использовать регистр сведений для подобных задач является методологической ошибкой. Если вам нужно просто регистрировать поток событий (например, историю изменений, логирование действий, фиксацию операций), то для этой цели гораздо лучше подходит Регистр накопления с видом Обороты.

Рассмотрим преимущества такого подхода:

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

Резюме и рекомендации

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

← На главную