При интеграции с внешними системами или загрузке данных из внешних файлов (Excel, CSV, JSON) разработчики часто сталкиваются с типовой задачей: необходимо выбрать документы из базы данных 1С, имея на руках не готовую ссылку, а лишь строковый или числовой код элемента справочника (например, код контрагента или номенклатуры).
В этой статье мы подробно проанализируем различные варианты решения данной задачи, рассмотрим плюсы и минусы каждого подхода, разберем влияние платформенных механизмов (кэширование, RLS, разыменование полей) и выясним, какой вариант является наиболее правильным с точки зрения стандартов разработки 1С и производительности СУБД.
Рассмотрим первый подход: перед выполнением запроса к документам мы предварительно находим ссылку на объект справочника во встроенном языке, а затем передаем ее в запрос как готовый параметр.
Разберем пример кода:
НужныйКонт = Справочники.Контрагенты.НайтиПоКоду("111");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ.РасходнаяНакладная КАК Док
|ГДЕ
| Док.Контрагент = &НужныйКонт";
Запрос.УстановитьПараметр("НужныйКонт", НужныйКонт);
РезультатЗапроса = Запрос.Выполнить();
На первый взгляд кажется, что выполняется два обращения к базе данных: первое — через метод НайтиПоКоду(), а второе — сам запрос. Однако проанализируем технические особенности работы платформы:
НайтиПоКоду() задействует внутренний кэш платформы 1С. Если объект уже считывался в текущей сессии или транзакции, платформа вернет ссылку из памяти без повторного обращения к СУБД.Index Seek) без необходимости делать какие-либо соединения с таблицей справочника.Контрагент станет составного типа, этот вариант не создаст лишних LEFT JOIN ко всем таблицам типа.Посмотрим на второй распространенный вариант, когда код передается непосредственно в условие ГДЕ запроса путем разыследования реквизита шапки документа:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ.РасходнаяНакладная КАК Док
|ГДЕ
| Док.Контрагент.Код = &КодКонтрагента";
Запрос.УстановитьПараметр("КодКонтрагента", "111");
РезультатЗапроса = Запрос.Выполнить();
Выясним, что происходит на стороне СУБД при выполнении такого запроса. При обращении «через точку» (Док.Контрагент.Код) платформа 1С автоматически трансформирует запрос, добавляя левое соединение (LEFT JOIN) с таблицей справочника Контрагенты.
Проанализируем особенности данного способа:
LEFT JOIN ко всем физическим таблицам, входящим в этот тип, что существенно снизит скорость выполнения.Разберем варианты, когда разработчики явно прописывают соединения таблиц или создают временные таблицы.
Пример с явным явным соединением:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
| ПО Контрагенты.Ссылка = Реализация.Контрагент
|ГДЕ
| Контрагенты.Код = &Код";
Запрос.УстановитьПараметр("Код", "111");
РезультатЗапроса = Запрос.Выполнить();
Сравним этот подход с вариантом обращения «через точку». С точки зрения итогового плана SQL Server (при отсутствии RLS и составных типов) варианты с явным INNER JOIN и разыменованием через точку дадут практически идентичный план выполнения.
Что касается использования временно таблиц для хранения одного элемента — это избыточная операция. Создание, индексация и удаление временной таблицы в СУБД требуют дополнительных ресурсов сервера БД. Временные таблицы эффективны при работе со списками и массивами данных, но не при поиске по одному значению.
Независимо от выбранного способа написания запроса, ключевым фактором скорости работы является наличие индекса по ссылочному реквизиту в документе.
Убедитесь, что для реквизита Контрагент в конфигураторе установлено свойство «Индексировать» (или «Индексировать с доп. упорядочиванием»), либо этот реквизит является первым полем в одном из критериев отбора или составных индексов таблицы. Без индекса СУБД будет вынуждена выполнять полное сканирование таблицы документов (Table Scan / Index Scan), что сведет на нет любую оптимизацию запроса.
Проанализируем ситуацию, когда из внешнего файла поступает не один код, а массив кодов контрагентов. В этом случае вызывать НайтиПоКоду() в цикле категорически запрещено (антипаттерн «Запрос в цикле»).
В такой ситуации правильным решением будет передача всего массива кодов в единый запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Контрагенты
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Код В (&МассивКодов);
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ.РасходнаяНакладная КАК Док
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Контрагенты КАК ВТ_Контрагенты
| ПО Док.Контрагент = ВТ_Контрагенты.Ссылка";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
РезультатЗапроса = Запрос.Выполнить();
Справочники.Контрагенты.НайтиПоКоду() и передача ссылки в параметр запроса.Док.Контрагент.Код) допустимо в простых сценариях, но создает лишние соединения, усложняет RLS и может замедлить работу при составных типах данных.