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

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

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

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

Вариант 1. Предварительный поиск ссылки через НайтиПоКоду()

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

Разберем пример кода:


НужныйКонт = Справочники.Контрагенты.НайтиПоКоду("111");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Док.Ссылка КАК Ссылка
    |ИЗ
    |	Документ.РасходнаяНакладная КАК Док
    |ГДЕ
    |	Док.Контрагент = &НужныйКонт";

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

На первый взгляд кажется, что выполняется два обращения к базе данных: первое — через метод НайтиПоКоду(), а второе — сам запрос. Однако проанализируем технические особенности работы платформы:

  1. Стандарты разработки 1С: Согласно официальным стандартам платформы, при отборе данных по одному известному значению реквизита рекомендуется предварительно получать ссылку и передавать ее в запрос параметром.
  2. Кэширование платформы: Вызов НайтиПоКоду() задействует внутренний кэш платформы 1С. Если объект уже считывался в текущей сессии или транзакции, платформа вернет ссылку из памяти без повторного обращения к СУБД.
  3. Оптимальный план запроса SQL: СУБД получает прямой параметр со ссылкой (GUID). В результате сформированный SQL-запрос выполняет точечный поиск по индексу (операция Index Seek) без необходимости делать какие-либо соединения с таблицей справочника.
  4. Защита от RLS и составных типов: Если в базе настроено ограничение прав на уровне записей (RLS), условие проверяется только для таблицы документов, не усложняя итоговый текст SQL-запроса условиями на справочник. Кроме того, если реквизит Контрагент станет составного типа, этот вариант не создаст лишних LEFT JOIN ко всем таблицам типа.

Вариант 2. Обращение к коду «через точку» в запросе

Посмотрим на второй распространенный вариант, когда код передается непосредственно в условие ГДЕ запроса путем разыследования реквизита шапки документа:


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

Запрос.УстановитьПараметр("КодКонтрагента", "111");
РезультатЗапроса = Запрос.Выполнить();

Выясним, что происходит на стороне СУБД при выполнении такого запроса. При обращении «через точку» (Док.Контрагент.Код) платформа 1С автоматически трансформирует запрос, добавляя левое соединение (LEFT JOIN) с таблицей справочника Контрагенты.

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

  1. Простота кода 1С: Код выглядит более компактным во встроенном языке, так как не требует предварительных вызовов функций поиска.
  2. Нагрузка на оптимизатор СУБД: В MS SQL Server или PostgreSQL такой запрос будет автоматически оптимизирован. Однако на стороне 1С генерируется соединение таблиц. Если реквизит имеет составной тип данных, платформа добавит LEFT JOIN ко всем физическим таблицам, входящим в этот тип, что существенно снизит скорость выполнения.
  3. Влияние RLS: Механизм RLS наложит дополнительные условия проверки прав сразу на две таблицы (документы и справочник), что при сложно настроенных правах может привести к тяжелым планам выполнения в SQL.

Вариант 3. Использование явного ВНУТРЕННЕГО СОЕДИНЕНИЯ или временных таблиц

Разберем варианты, когда разработчики явно прописывают соединения таблиц или создают временные таблицы.

Пример с явным явным соединением:


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

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

Сравним этот подход с вариантом обращения «через точку». С точки зрения итогового плана SQL Server (при отсутствии RLS и составных типов) варианты с явным INNER JOIN и разыменованием через точку дадут практически идентичный план выполнения.

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

Критически важное условие: Индексация реквизита

Независимо от выбранного способа написания запроса, ключевым фактором скорости работы является наличие индекса по ссылочному реквизиту в документе.

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

Что делать, если кодов несколько (список или массив)?

Проанализируем ситуацию, когда из внешнего файла поступает не один код, а массив кодов контрагентов. В этом случае вызывать НайтиПоКоду() в цикле категорически запрещено (антипаттерн «Запрос в цикле»).

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


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

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

Подведем итоги

← На главную