Как получить запись подчиненного регистра по регистратору и номеру строки в 1С?

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

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

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

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

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

Способ 1. Получение записи по индексу из прочитанного набора записей

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

Посмотрим на пример кода, реализующего этот подход:


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

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

// Читаем все записи данного регистратора из базы данных в оперативную память
ТекНЗ.Прочитать();

// Номер строки в базе данных начинается с 1, а индекс в коллекции 1С — с 0.
// Поэтому для получения нужной строки вычитаем единицу из номера записи.
ИндексНужнойСтроки = стрЗаписьПустыхИнв.НомерЗаписи - 1;

// Обязательно проверяем, что индекс входит в диапазон прочитанных записей
Если ИндексНужнойСтроки >= 0 И ИндексНужнойСтроки < ТекНЗ.Количество() Тогда
    
    // Получаем конкретную запись по индексу без использования циклов
    ТекущаяЗапись = ТекНЗ[ИндексНужнойСтроки];
    
    // Теперь мы можем прочитать свойства или изменить их
    ТекущаяЗапись.Значение = НовоеЗначениеСвойства;
    
    // Записываем измененный набор обратно в базу данных
    ТекНЗ.Записать();
    
КонецЕсли;

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

Способ 2. Оптимальное получение данных через запрос

Если вашей задачей является исключительно получение (чтение) параметров конкретной строки без последующей перезаписи всего набора, использование объекта НаборЗаписей будет избыточным. Гораздо правильнее и быстрее обратиться к СУБД с помощью точечного запроса.

Рассмотрим подробнее, как составить такой запрос:


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

// Передаем параметры в запрос
Запрос.УстановитьПараметр("Регистратор", СсылкаНаДокумент);
// Указываем физический номер строки в регистре (начиная с 1)
Запрос.УстановитьПараметр("НомерСтроки", НужныйНомерСтроки);

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

Если Не РезультатЗапроса.Пустой() Тогда
    Выборка = РезультатЗапроса.Выбрать();
    Выборка.Следующий();
    
    // Успешно считали данные конкретной строки
    Сообщить("Значение ресурса: " + Выборка.Ресурс1);
КонецЕсли;

Использование запроса позволяет СУБД применить оптимальный план выполнения и вернуть на сервер приложений только одну строчку, минимизируя трафик и нагрузку на дисковую подсистему.

Архитектурная причина ограничения платформы

Разберем, почему разработчики платформы «1С:Предприятие 8» заблокировали возможность отбора по иным полям, кроме регистратора. Все дело в концепции работы с транзакционными данными.

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

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

Важный нюанс: «Номер строки» регистра и «Номер строки» документа

Начинающим разработчикам очень важно не путать два абсолютно разных понятия:

  1. НомерСтроки в регистре — это автоматически присваиваемое платформой числовое поле (начиная с 1) при физической записи набора в таблицу СУБД. Порядок строк определяется тем, как записи были добавлены в коллекцию набора перед вызовом метода Записать().
  2. Номер строки табличной части документа — это реквизит табличной части самого документа-источника.

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

Решение для надежной связи: если по бизнес-логике вам крайне необходимо жестко связать конкретную строку табличной части документа со строкой в регистре, не полагайтесь на системный НомерСтроки. Добавьте в ваш регистр измерение или реквизит с типом УникальныйИдентификатор или специальный числовой КлючСвязиСтроки. При проведении документа генерируйте этот ключ для строки ТЧ и записывайте его в соответствующее поле регистра.

Специфика перезаписи данных

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

  1. Удалит абсолютно все существующие строки этого регистратора из таблицы базы данных.
  2. Запишет весь измененный набор заново.

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

← На главную