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

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

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

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

Выясняем причину: почему нельзя установить отбор по измерению?

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

Это связано с тем, как СУБД хранит и перезаписывает данные. На уровне базы данных все движения одного документа-регистратора представляют собой единую неделимую логическую группу (гранулу данных). Когда мы записываем набор записей с установленным отбором по конкретному регистратору, платформа выполняет транзакционную операцию: она полностью удаляет все старые записи этого регистратора в таблице БД и записывает новые из нашего набора. Если бы платформа позволила наложить отбор по номенклатуре внутри этого регистратора, это привело бы к частичной перезаписи движений документа, нарушению целостности транзакций и потенциальному зацикливанию блокировок.

Решение 1: Классический обходной путь (для версий платформы ниже 8.3.26)

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

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


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

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

// 3. Считываем все существующие движения этого документа из базы данных в память
НаборЗаписей.Прочитать();

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

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

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

// 6. Записываем весь измененный пакет данных обратно в СУБД. 
// По умолчанию второй параметр Замещать равен Истина, что полностью обновит движения регистратора.
НаборЗаписей.Записать();

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

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

Решение 2: Революционные расширенные режимы замещения (в платформе 8.3.26 и выше)

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

Теперь мы можем точечно обновлять или удалять конкретные строки в подчиненном регистре сведений без полной перезаписи всей «гранулы» движений регистратора! Для этого используется новое свойство РасширенныеРежимыЗамещения и системное перечисление РежимЗамещения.

Давайте проанализируем, как с помощью этой новой возможности точечно изменить строку в подчиненном регистре:


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

// Активируем использование расширенных режимов замещения
НаборЗаписей.РасширенныеРежимыЗамещения = Истина;

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

// Записываем набор, используя новый режим замещения - Обновление.
// При этом обновятся ТОЛЬКО те строки, номера которых мы явно указали в наборе. 
// Остальные записи регистратора в СУБД останутся нетронутыми!
НаборЗаписей.Записать(РежимЗамещения.Обновление);

Для удаления конкретной строки, например, строки под номером 3, мы можем использовать режим удаления:


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

СтрокаДляУдаления = НаборЗаписей.Добавить();
СтрокаДляУдаления.НомерСтроки = 3; // Строка, которую нужно удалить из СУБД

// Платформа точечно удалит строку №3 и автоматически перенумерует последующие записи в БД
НаборЗаписей.Записать(РежимЗамещения.Удаление);

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

Решение 3: Архитектурные альтернативы

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

Вариант А: Перевод регистра сведений в режим «Независимый»

Если цены номенклатуры должны легко изменяться различными обработками, импортом прайс-листов или корректировками без жесткой привязки к конкретному документу поставки, то регистр логично сделать независимым. В этом случае для точечной работы с записями мы сможем использовать объект МенеджерЗаписи, который позволяет прочитать, изменить и записать одну-единственную строку по ключевым измерениям:


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

МенеджерЗаписи.Цена = НоваяЦена;
МенеджерЗаписи.Записать();

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

Вариант Б: Добавление корректирующего документа в список регистраторов

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

Вариант В: Использование универсального корректирующего регистратора

В крупных тиражных решениях от 1С (таких как ERP, Комплексная автоматизация) часто применяется механизм универсального регистратора. Создается специальный служебный документ (например, КорректировкаЗаписейРегистров), который программно наполняется нужными записями движений и проводится. Это обеспечивает ссылочную целостность базы данных и оставляет понятный аудиторский след для аналитиков и бухгалтеров.

← На главную