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

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

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

В этой статье мы подробно разберем, почему стандартный отбор в наборе записей не работает по сложным условиям, и рассмотрим три практических способа решения этой задачи: от классических обходов для старых конфигураций до ультрасовременных высокопроизводительных методов, появившихся в последних версиях платформы 1С, а для сокращения объема накопленных исторических данных в регистрах может использоваться программа многопоточной свёртки 1С:ERP.

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

Для начала проанализируем, как устроен объект НаборЗаписей и его свойство Отбор. На уровне платформы 1С накладывает на этот инструмент критически важное ограничение:

Отбор в наборе записей может работать только на строгое равенство.

Это означает, что вы не можете использовать такие операторы, как «В списке» (В), «В иерархии» (В ИЕРАРХИИ) или «Не равно». Платформа требует передать в отбор конкретное, единственное значение для каждого измерения, по которому накладывается фильтр.

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


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

Выясним причину, почему этот код не решит задачу модификации цен для товаров внутри группы. Когда мы передаем в метод Установить() ссылку на группу номенклатуры (папку), система отправляет к СУБД запрос с условием точного равенства: Номенклатура = &СсылкаНаГруппу. База данных будет искать записи, у которых в поле «Номенклатура» записана именно сама папка. Поскольку в регистр записываются только конечные элементы (товары), запрос вернет пустой набор записей. Данные по элементам, которые физически входят в эту группу, в набор не попадут.

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

Способ 1. Современный метод — использование новых режимов замещения (для платформ 8.3.26 / 8.3.27 и выше)

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

Ранее любая запись набора без фильтра по ключевым измерениям приводила к полной очистке регистра (или очистке по границам установленного отбора). Теперь мы можем использовать новые режимы:

  1. РежимЗамещения.Слияние (появился в 8.3.26) — сопоставляет записи набора с записями в базе данных по ключевым полям. Если запись уже существует, она обновляется; если ее нет — она добавляется. Остальные записи в регистре остаются нетронутыми.
  2. РежимЗамещения.Обновление (появился в 8.3.27) — обновляет только те записи в базе данных, которые уже существуют и совпадают по ключам с записями в передаваемом наборе. Новые записи не создаются, сторонние данные не затираются.

Разберем по шагам алгоритм работы с этим методом:

  1. С помощью запроса выбираем из регистра только те записи, которые соответствуют нашему условию (например, входят в нужную группу номенклатуры и соответствуют дате).
  2. Выгружаем результат запроса в таблицу значений.
  3. В цикле по таблице значений меняем необходимые ресурсы (например, сумму себестоимости).
  4. Загружаем измененную таблицу в пустой НаборЗаписей без установки каких-либо отборов.
  5. Записываем набор в режиме слияния или обновления.

Посмотрим на практический пример реализации этого алгоритма:


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

Запрос.УстановитьПараметр("Период", ВыбраннаяДата);
Запрос.УстановитьПараметр("ГруппаНоменклатуры", СсылкаНаГруппу);

ТаблицаДанных = Запрос.Выполнить().Выгрузить();

// Шаг 2. Модифицируем данные в таблице значений
Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл
    // Например, увеличиваем сумму себестоимости на 10%
    СтрокаТаблицы.Сумма = СтрокаТаблицы.Сумма * 1.1; 
КонецЦикла;

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

// Ключевой момент: пишем без отборов, используя РежимЗамещения.Слияние
НаборЗаписей.Записать(РежимЗамещения.Слияние);

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

Способ 2. Классический метод А — Использование Менеджера Записи (для небольших объемов данных)

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

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

Проанализируем пример кода для этого метода:


// Получаем только ключевые поля для точечной идентификации записей
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПлановаяСебестоимость.Период КАК Период,
    |	ПлановаяСебестоимость.Номенклатура КАК Номенклатура
    |ИЗ
    |	РегистрСведений.ПлановаяСебестоимостьНоменклатуры КАК ПлановаяСебестоимость
    |ГДЕ
    |	ПлановаяСебестоимость.Период = &Период
    |	И ПлановаяСебестоимость.Номенклатура В ИЕРАРХИИ(&ГруппаНоменклатуры)";

Запрос.УстановитьПараметр("Период", ВыбраннаяДата);
Запрос.УстановитьПараметр("ГруппаНоменклатуры", СсылкаНаГруппу);

Выборка = Запрос.Выполнить().Выбрать();

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

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

Способ 3. Классический метод Б — Покомпонентная перезапись через Набор Записей в цикле

Что делать, если объем данных велик (десятки и сотни тысяч записей), а версия платформы не позволяет применить РежимЗамещения.Слияние? В этом случае стандарты разработки фирмы «1С» рекомендуют использовать пакетную обработку данных порциями с помощью набора записей с жестким отбором по ключевым измерениям.

Рассмотрим этот алгоритм подробнее:

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

Реализуем данный подход программно:


// Выбираем список уникальной номенклатуры для точечного отбора
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	ПлановаяСебестоимость.Номенклатура КАК Номенклатура
    |ИЗ
    |	РегистрСведений.ПлановаяСебестоимостьНоменклатуры КАК ПлановаяСебестоимость
    |ГДЕ
    |	ПлановаяСебестоимость.Период = &Период
    |	И ПлановаяСебестоимость.Номенклатура В ИЕРАРХИИ(&ГруппаНоменклатуры)";

Запрос.УстановитьПараметр("Период", ВыбраннаяДата);
Запрос.УстановитьПараметр("ГруппаНоменклатуры", СсылкаНаГруппу);

Выборка = Запрос.Выполнить().Выбрать();

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

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

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

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

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

← На главную