Работа с регистрами сведений, подчиненными регистратору, в 1С имеет ряд существенных отличий от работы с независимыми регистрами. Основная сложность заключается в том, что запись в такие регистры жестко привязана к документу-регистратору, и любое нарушение правил уникальности или логики отбора приводит к ошибкам. В этой статье мы подробно разберем, почему возникают ошибки при записи набора записей, как их диагностировать и как оптимизировать процесс загрузки данных.
Рассмотрим ситуацию, когда программист пытается программно создать записи в регистре. Одной из самых распространенных ошибок является попытка установить отбор по одному документу, а в саму запись передать другой документ или вообще не указать его. Проанализируем пример некорректного кода:
НаборЗаписей = РегистрыСведений.СебестоимостьНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка1);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Регистратор = ДокументСсылка2; // ОШИБКА! Регистратор должен совпадать с отбором
НоваяЗапись.Номенклатура = СсылкаНоменклатуры;
НаборЗаписей.Записать();
Важно помнить: когда мы создаем НаборЗаписей с установленным отбором по Регистратор, система ожидает, что все записи внутри этого набора будут принадлежать исключительно этому регистратору. Если в поле Регистратор новой записи будет передана ссылка на другой документ, при вызове метода Записать() система выдаст ошибку несоответствия отбору.
Выясним причину другой критической ошибки, которая часто ставит в тупик начинающих разработчиков. Сообщение "Запись с такими ключевыми полями существует!" означает, что в рамках одного регистратора вы пытаетесь добавить две или более строк, у которых совпадает набор измерений и период (если регистр периодический).
Для регистра, подчиненного регистратору, уникальность определяется комбинацией следующих полей:
Если в вашем коде при загрузке из внешнего источника (например, Excel) одна и та же номенклатура встречается дважды для одного и того же документа и магазина, запись набора будет невозможна. Чтобы избежать этого, необходимо либо предварительно суммировать данные (сворачивать таблицу значений), либо добавлять дополнительное измерение, делающее запись уникальной (например, номер строки).
Рассмотрим правильную последовательность действий при работе с набором записей. Разберем по шагам, как нужно действовать, чтобы избежать блокировок и некорректных данных:
СоздатьНаборЗаписей().Регистратору. Это критично для подчиненных регистров.НаборЗаписей.Записать(Истина). По умолчанию параметр Замещать равен Истина, что означает полное удаление старых записей этого регистратора перед записью новых.Записать().Пример корректного кода:
РегистраторСсылка = Документы.УстановкаСебестоимости.НайтиПоНомеру("РР-00000012", ТекущаяДата());
НаборЗаписей = РегистрыСведений.СебестоимостьНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(РегистраторСсылка);
// Добавляем записи
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = РегистраторСсылка.Дата;
НоваяЗапись.Регистратор = РегистраторСсылка;
НоваяЗапись.Магазин = СсылкаМагазин;
НоваяЗапись.Номенклатура = СсылкаНоменклатура;
НоваяЗапись.Цена = НужнаяЦена;
НоваяЗапись.Активность = Истина;
Попытка
НаборЗаписей.Записать();
Исключение
Сообщить("Не удалось записать данные: " + ОписаниеОшибки());
КонецПопытки;
Проанализируем ситуацию, когда данных очень много (тысячи строк из Excel). Использование методов НайтиПоНаименованию() или НайтиПоКоду() внутри цикла — это крайне неэффективный подход, так как каждое такое обращение генерирует отдельный запрос к серверу базы данных. Рассмотрим более производительный метод работы через временные таблицы.
Рекомендуемая стратегия:
ТаблицаЗначений.Номенклатура (по коду или артикулу) и получаем готовые ссылки.Посмотрим на пример архитектуры такого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеИзФайла ИЗ &ТЗ КАК ТЗ;
|ВЫБРАТЬ
| СпрНоменклатура.Ссылка КАК Номенклатура,
| ВТ.Цена КАК НоваяЦена,
| ЕСТЬNULL(СебестоимостьСрезПоследних.Цена, 0) КАК СтараяЦена
|ИЗ
| ВТ_ДанныеИзФайла КАК ВТ
|ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
| ПО СпрНоменклатура.Код = ВТ.Код
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СебестоимостьНоменклатуры.СрезПоследних(&Период, ) КАК СебестоимостьСрезПоследних
| ПО СебестоимостьСрезПоследних.Номенклатура = СпрНоменклатура.Ссылка";
Запрос.УстановитьПараметр("ТЗ", ТаблицаДанныхИзExcel);
Запрос.УстановитьПараметр("Период", ТекущаяДата());
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Такой подход позволяет сократить время обработки данных в десятки раз, так как основная нагрузка по поиску и сопоставлению ложится на SQL-сервер, который справляется с этим максимально быстро.
При записи движений стоит обратить внимание на свойство Активность. Если установить НоваяЗапись.Активность = Ложь, то запись попадет в базу данных, но не будет участвовать в итогах и не будет видна в виртуальных таблицах типа СрезПоследних. Это удобно для механизмов отложенного проведения документов.
Также помните о блокировках данных. При вызове метода Записать() система устанавливает исключительную блокировку на записи по данному регистратору. Чтобы не мешать работе других пользователей, старайтесь минимизировать время между заполнением набора и его записью, а также избегайте длительных вычислений внутри транзакции записи.
Подводя итог, можно сказать, что стабильная работа с подчиненными регистрами сведений требует внимательного отношения к Отбору, контроля уникальности по Измерениям и использования Запросов для предварительной подготовки данных. Рассмотрим эти правила как стандарт при разработке любых механизмов загрузки и корректировки движений в 1С.