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

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

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

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

В чем опасность запроса в цикле?

Каждое обращение к базе данных из кода «1С» — это сетевой запрос, требующий времени на установку соединения, компиляцию плана запроса СУБД, получение данных и их передачу обратно на сервер приложений. Если у нас в цикле обходится 1000 элементов номенклатуры, программа сделает 1000 отдельных запросов к СУБД. Это гарантированно приведет к зависанию системы. Наша главная цель — получить все необходимые данные одним запросом к базе данных, а затем обработать их в оперативной памяти.

Метод 1. Использование метода НайтиСледующий() в выборке запроса

Рассмотрим первый подход, предложенный на форуме. Суть метода заключается в том, что мы сначала делаем один запрос к регистру сведений МестаХраненияНоменклатуры, отбирая записи по всей иерархии нужной группы номенклатуры. Затем мы обходим справочник циклом и ищем соответствующую запись в выборке запроса.

Посмотрим на пример реализации данного подхода:


// Выбираем элементы справочника Номенклатура по группе
ВыборкаНоменклатуры = Справочники.Номенклатура.Выбрать(ГруппаНоменклатуры);

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

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

Пока ВыборкаНоменклатуры.Следующий() Цикл
    Если ВыборкаНоменклатуры.ЭтоГруппа Тогда
        Продолжить;
    КонецЕсли;

    // Сбрасываем выборку к началу для нового поиска
    ВыборкаЗапроса.Сбросить();
    
    // Формируем структуру отбора по текущему элементу номенклатуры
    Отбор = Новый Структура("Номенклатура", ВыборкаНоменклатуры.Ссылка);
    
    Если ВыборкаЗапроса.НайтиСледующий(Отбор) Тогда
        Сообщить("Для товара " + ВыборкаНоменклатуры.Наименование + " запись в регистре ЕСТЬ.");
    Иначе
        Сообщить("Для товара " + ВыборкаНоменклатуры.Наименование + " записи в регистре НЕТ.");
    КонецЕсли;
КонецЦикла;

Анализ эффективности: Данный метод решает проблему запроса в цикле, но имеет серьезный скрытый недостаток. Метод НайтиСледующий() при каждом вызове Сбросить() начинает обход выборки с самого начала. Если в выборке содержится большое количество записей, сложность алгоритма возрастает пропорционально квадрату количества элементов (алгоритмическая сложность составляет O(N^2)). На реальных базах данных с тысячами номенклатурных позиций этот код будет выполняться крайне медленно.

Метод 2. Использование коллекции Соответствие (Map) для быстрого поиска в памяти

Для того чтобы обойти проблему медленного линейного поиска в выборке запроса, мы можем применить объект платформы Соответствие (в терминах других языков программирования — хэш-таблица или ассоциативный массив). Поиск элементов в объекте Соответствие происходит практически мгновенно (алгоритмическая сложность составляет O(1)) независимо от объема данных.

Давайте разберем по шагам, как реализовать этот высокопроизводительный алгоритм:

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

Рассмотрим программный код этого решения:


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

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

// Шаг 2. Заполняем Соответствие в оперативной памяти
ЗаписиРегистра = Новый Соответствие;
ВыборкаЗапроса = РезультатЗапроса.Выбрать();
Пока ВыборкаЗапроса.Следующий() Цикл
    ЗаписиРегистра.Вставить(ВыборкаЗапроса.Номенклатура, ВыборкаЗапроса.МестоХранения);
КонецЦикла;

// Шаг 3. Быстрый обход справочника с поиском в памяти
ВыборкаНоменклатуры = Справочники.Номенклатура.Выбрать(ГруппаНоменклатуры);
Пока ВыборкаНоменклатуры.Следующий() Цикл
    Если ВыборкаНоменклатуры.ЭтоГруппа Тогда
        Продолжить;
    КонецЕсли;
    
    // Извлекаем значение по ключу-ссылке. Операция выполняется мгновенно
    МестоХранения = ЗаписиРегистра[ВыборкаНоменклатуры.Ссылка];
    
    Если МестоХранения <> Неопределено Тогда
        Сообщить("Товар: " + ВыборкаНоменклатуры.Наименование + " | Место хранения: " + МестоХранения);
    Иначе
        Сообщить("Товар: " + ВыборкаНоменклатуры.Наименование + " | Место хранения НЕ ЗАДАНО");
    КонецЕсли;
КонецЦикла;

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

Метод 3. Идеальный вариант — Левое соединение таблиц в одном Запросе

Наиболее правильным, профессиональным и рекомендуемым стандартом «1С» решением является полный перенос логики сопоставления данных на сторону СУБД. Нам вообще не следует использовать метод Справочники.Номенклатура.Выбрать() в коде. Платформа «1С:Предприятие 8» оптимизирована для работы с базами данных, поэтому соединение справочника и регистра сведений нужно выполнять с помощью ЛЕВОГО СОЕДИНЕНИЯ в запросе.

Давайте проанализируем, как работает левое соединение. Запрос выберет все элементы справочника Номенклатура из указанной группы, и для каждого элемента попытается найти соответствующую строчку в регистре сведений. Если запись в регистре есть, поля регистра будут заполнены. Если записи нет, поля регистра примут значение NULL.

Рассмотрим оптимальный и лаконичный текст запроса:


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

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

Пока Выборка.Следующий() Цикл
    Если Выборка.ЕстьЗаписьВРегистре Тогда
        Сообщить("Товар: " + Выборка.Номенклатура + " | Найдено место хранения: " + Выборка.МестоХранения);
    Иначе
        Сообщить("Товар: " + Выборка.Номенклатура + " | В регистре нет записи!");
    КонецЕсли;
КонецЦикла;

Преимущества решения через ЛЕВОЕ СОЕДИНЕНИЕ:

Дополнительный нюанс: Работа с произвольными списками (Временные Таблицы)

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

  1. Передайте массив или таблицу значений с номенклатурой в качестве параметра запроса.
  2. Внутри запроса поместите этот список во Временную Таблицу.
  3. Обязательно проиндексируйте временную таблицу по полю Номенклатура для оптимизации последующего соединения.
  4. Выполните левое соединение временной таблицы с регистром сведений.

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

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

← На главную