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

Программист 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

Разбор типичной ошибки: Несоответствие регистратора в отборе

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


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

НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Регистратор = ДокументСсылка2; // ОШИБКА! Регистратор должен совпадать с отбором
НоваяЗапись.Номенклатура = СсылкаНоменклатуры;
НаборЗаписей.Записать();

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

Проблема уникальности: Ошибка "Запись с такими ключевыми полями существует"

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

Для регистра, подчиненного регистратору, уникальность определяется комбинацией следующих полей:

  1. Период (если установлен режим периодичности).
  2. Регистратор (автоматически включается в ключ).
  3. Измерения (все поля, помеченные как измерения в конфигураторе).

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

Пошаговый алгоритм правильной записи в регистр

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

  1. Создание набора: Используем метод СоздатьНаборЗаписей().
  2. Установка отбора: Обязательно устанавливаем отбор по Регистратору. Это критично для подчиненных регистров.
  3. Очистка существующих записей: Если мы хотим полностью перезаписать движения документа, достаточно вызвать НаборЗаписей.Записать(Истина). По умолчанию параметр Замещать равен Истина, что означает полное удаление старых записей этого регистратора перед записью новых.
  4. Заполнение данных: В цикле добавляем новые строки в набор.
  5. Финальная запись: Вызываем Записать().

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


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

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

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

Попытка
    НаборЗаписей.Записать();
Исключение
    Сообщить("Не удалось записать данные: " + ОписаниеОшибки());
КонецПопытки;

Оптимизация: Использование запросов для больших объемов данных

Проанализируем ситуацию, когда данных очень много (тысячи строк из Excel). Использование методов НайтиПоНаименованию() или НайтиПоКоду() внутри цикла — это крайне неэффективный подход, так как каждое такое обращение генерирует отдельный запрос к серверу базы данных. Рассмотрим более производительный метод работы через временные таблицы.

Рекомендуемая стратегия:

  1. Загружаем данные из файла в ТаблицаЗначений.
  2. Передаем эту таблицу в запрос как параметр.
  3. В запросе соединяем таблицу с справочником Номенклатура (по коду или артикулу) и получаем готовые ссылки.
  4. Там же, в запросе, можно соединиться с текущими остатками или ценами регистра, чтобы записывать только измененные данные.

Посмотрим на пример архитектуры такого запроса:


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

Запрос.УстановитьПараметр("ТЗ", ТаблицаДанныхИзExcel);
Запрос.УстановитьПараметр("Период", ТекущаяДата());

РезультатЗапроса = Запрос.Выполнить().Выбрать();

Такой подход позволяет сократить время обработки данных в десятки раз, так как основная нагрузка по поиску и сопоставлению ложится на SQL-сервер, который справляется с этим максимально быстро.

Дополнительные нюансы: Активность и Блокировки

При записи движений стоит обратить внимание на свойство Активность. Если установить НоваяЗапись.Активность = Ложь, то запись попадет в базу данных, но не будет участвовать в итогах и не будет видна в виртуальных таблицах типа СрезПоследних. Это удобно для механизмов отложенного проведения документов.

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

Подводя итог, можно сказать, что стабильная работа с подчиненными регистрами сведений требует внимательного отношения к Отбору, контроля уникальности по Измерениям и использования Запросов для предварительной подготовки данных. Рассмотрим эти правила как стандарт при разработке любых механизмов загрузки и корректировки движений в 1С.

← На главную