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

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

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

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

Анализ исходного кода и причина ошибки

Давайте посмотрим на первоначальный код, который приводит к некорректной работе. Задача — изменить реквизит СрокГарантии у номенклатуры из группы, которую пользователь выбрал в реквизите обработки Объект.ГруппаНоменклатуры.


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

В чем здесь коренная ошибка?

  1. Запрос выбирает все данные. В тексте запроса отсутствует секция ГДЕ, которая отвечает за фильтрацию. Конструкция ИЗ Справочник.Номенклатура говорит системе: "дай мне абсолютно все записи из справочника Номенклатура". В результате, если в базе 100 000 позиций, все они будут получены из базы данных и переданы для дальнейшей обработки.
  2. Неверная логика проверки в цикле. Условие Если НЕ ЗначениеЗаполнено(Объект.ГруппаНоменклатуры) проверяет лишь то, выбрал ли пользователь группу в реквизите формы. Оно никак не связано с текущим элементом из выборки (ВыборкаДетальныеЗаписи). Эта проверка на каждой итерации цикла просто проверяет одно и то же — заполнено ли поле на форме. Более того, ветка Иначе будет выполняться для каждого элемента номенклатуры, если группа на форме выбрана. Именно поэтому обработка и меняет данные у всех.

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

Решение 1: Правильная фильтрация в запросе (рекомендуемый способ)

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

Давайте изменим запрос, чтобы он сразу отбирал только нужную номенклатуру.

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

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


Процедура УстановитьНаСервере()
    // В первую очередь проверяем, выбрал ли пользователь группу.
    // Делать это нужно один раз, до запроса, а не в цикле.
    Если НЕ ЗначениеЗаполнено(Объект.ГруппаНоменклатуры) Тогда
        Сообщить("Не выбрана группа справочника Номенклатура!");   
        Возврат;
    КонецЕсли;

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

    // Передаем в параметр "&Группа" значение, выбранное пользователем
    Запрос.УстановитьПараметр("Группа", Объект.ГруппаНоменклатуры);
    
    РезультатЗапроса = Запрос.Выполнить();
    
    Выборка = РезультатЗапроса.Выбрать();      
     
    Пока Выборка.Следующий() Цикл  
        // Получаем объект только для тех элементов, что вернул запрос
        СправочникОбъект = Выборка.Ссылка.ПолучитьОбъект();                      
                
        // Устанавливаем новый срок гарантии из соответствующего поля обработки
        СправочникОбъект.СрокГарантии = Объект.СрокГарантии;
        
        // Записываем измененный объект
        СправочникОбъект.Записать(); 
    КонецЦикла;
    
    Сообщить("Срок гарантии успешно изменен для товаров выбранной группы!");
КонецПроцедуры

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

Решение 2: Фильтрация в цикле (неэффективный и неполный способ)

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

Для проверки принадлежности элемента к группе можно обратиться к его реквизиту Родитель.


// ... код запроса без условия ГДЕ ...
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();

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

Недостатки этого подхода:

Дополнительные улучшения: Транзакции и опциональный фильтр

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

Использование транзакций

Когда вы выполняете массовое изменение данных, есть риск, что в середине процесса произойдет ошибка (например, из-за блокировки объекта другим пользователем). В этом случае часть данных уже будет изменена, а часть — нет. Чтобы обеспечить целостность данных, такие операции следует оборачивать в транзакцию. Транзакция гарантирует, что либо все изменения будут успешно применены (ЗафиксироватьТранзакцию), либо все они будут отменены в случае ошибки (ОтменитьТранзакцию).


НачатьТранзакцию();
Попытка
    Пока Выборка.Следующий() Цикл  
        СправочникОбъект = Выборка.Ссылка.ПолучитьОбъект();
        СправочникОбъект.СрокГарантии = Объект.СрокГарантии;
        СправочникОбъект.Записать(); 
    КонецЦикла;
    
    ЗафиксироватьТранзакцию();
    Сообщить("Операция успешно завершена!");
Исключение
    ОтменитьТранзакцию();
    Сообщить("Произошла ошибка. Все изменения отменены.");
    // Здесь можно вывести информацию об ошибке
    ВызватьИсключение;
КонецПопытки;

Необязательный фильтр по группе

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


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

// Устанавливаем параметры. Если группа не выбрана, ее ссылка будет пустой.
Запрос.УстановитьПараметр("Группа", Объект.ГруппаНоменклатуры);
// Передаем булево значение: Истина, если группа не выбрана, и Ложь, если выбрана.
Запрос.УстановитьПараметр("ВсеГруппы", НЕ ЗначениеЗаполнено(Объект.ГруппаНоменклатуры));

РезультатЗапроса = Запрос.Выполнить();
// ... дальнейшая обработка ...

Как работает это условие: (&ВсеГруппы ИЛИ Номенклатура.Ссылка В ИЕРАРХИИ(&Группа))

Таким образом, правильное использование языка запросов 1С не только значительно ускоряет работу кода, но и делает его более гибким и надежным.

← На главную