Разыменование составных ссылок через точку в запросах 1С: откуда берутся лишние JOIN
База данных - HighLoad оптимизация
Обращение к реквизитам через точку — одна из самых удобных возможностей языка запросов 1С. Само по себе оно не является проблемой. При разыменовании поля составного ссылочного типа без явного ограничения типа платформа при SQL-трансляции формирует соединения с таблицами всех ссылочных типов, входящих в состав этого поля.
Количество этих соединений определяется составом типа. Если в поле разрешено хранить ссылки на шесть видов документов, при получении реквизита через точку в SQL формируются соединения с шестью таблицами документов. Сам факт появления этих JOIN не зависит от объема данных; отдельно оценивается их стоимость в конкретном плане выполнения. Ниже — исходный запрос, фактическая SQL-трансляция и результат после явного ограничения типа.
Сам прием с «ВЫРАЗИТЬ» не новый и описан в рекомендациях 1С. Здесь интерес представляет именно фактическая SQL-трансляция и практический замер. Рекомендация 1С:ИТС о разыменовании составных ссылочных полей
1. Что скрывается за составным типом
В рассматриваемом примере у документа «Заказ поставщику» есть реквизит «ДокументОснование». Его тип включает ссылки на несколько видов документов: заказ клиента, заказ на перемещение, приобретение товаров и другие документы.
Для прикладного разработчика это выглядит как одно поле:
ЗаказПоставщику.ДокументОснование
На уровне таблицы базы данных значение поля составного ссылочного типа представлено служебными составляющими, которые позволяют определить тип значения и конкретный объект. Пока запрос получает только саму ссылку, обращаться к таблице связанного объекта не требуется. Выражение вида «ДокументОснование.Номер» требует получить реквизит уже из таблицы связанного документа.
Если ссылочный тип составной и конкретный тип заранее не ограничен, при разыменовании платформа формирует соединения с таблицами всех ссылочных типов, входящих в состав этого поля. Именно это происходит в рассматриваемом запросе.
2. Исходный запрос
Нужно получить документ-основание заказа поставщику и номер этого основания:
ВЫБРАТЬ
ЗаказПоставщику.ДокументОснование КАК ДокументОснование,
ЗаказПоставщику.ДокументОснование.Номер КАК ДокументОснованиеНомер
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ГДЕ
ЗаказПоставщику.Дата >= &Дата
В тексте запроса нет сложных вычислений. Ключевое место — обращение:
ЗаказПоставщику.ДокументОснование.Номер
В конкретной задаче номер был нужен только тогда, когда основание — «Документ.ЗаказКлиента». Для остальных типов значение номера не использовалось. Исходный запрос этого ограничения не содержал, поэтому при SQL-трансляции платформа учла все шесть допустимых типов и сформировала соединения со всеми шестью таблицами документов.
Это важная оговорка: дальнейшее изменение — не механическая замена синтаксиса «точки» на «ВЫРАЗИТЬ». Мы уточняем запрос в соответствии с реальным условием задачи.
3. Что получилось в SQL
В тестовой конфигурации в состав реквизита «ДокументОснование» входило шесть видов документов. При прямом обращении к «ДокументОснование.Номер» платформа сформировала шесть LEFT OUTER JOIN — по одному для каждого допустимого типа — и выражение CASE, выбирающее номер из таблицы фактического типа основания.
Ниже — SQL, полученный через используемую мной консоль запросов. Часть служебных имен в выводе заменена на имена реквизитов 1С, при этом физические имена таблиц и некоторые служебные поля СУБД сохранены.

Рис. 1. SQL до изменения: шесть LEFT OUTER JOIN и CASE для выбора номера
На скриншоте видно:
- шесть соединений с физическими таблицами документов (_Document329, _Document324, _Document323, _Document322, _Document321 и _Document331);
- CASE, который выбирает поле _Number из нужной таблицы в зависимости от типа ссылки;
- дополнительные служебные условия платформы. Их конкретный вид зависит от конфигурации и настроек информационной базы.
Шесть JOIN в данном SQL — прямое следствие разыменования составной ссылки без ограничения типа. Их наличие само по себе не определяет время выполнения: фактическая стоимость зависит от выбранного плана, объема данных, индексов и статистики СУБД. В прикладной задаче требуется только один тип, поэтому это ограничение передаем платформе явно.
4. Ограничиваем тип через «ВЫРАЗИТЬ»
По условиям задачи номер нужен только для заказа клиента, поэтому тип указываем прямо в запросе:
ВЫБРАТЬ
ЗаказПоставщику.ДокументОснование КАК ДокументОснование,
ВЫРАЗИТЬ(
ЗаказПоставщику.ДокументОснование
КАК Документ.ЗаказКлиента
).Номер КАК ДокументОснованиеНомер
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ГДЕ
ЗаказПоставщику.Дата >= &Дата
После явного ограничения типа SQL содержит одно соединение с таблицей «ЗаказКлиента» вместо шести соединений со всеми допустимыми типами.

Рис. 2. SQL после «ВЫРАЗИТЬ»: одно соединение с таблицей нужного документа
На втором скриншоте остается один LEFT OUTER JOIN с _Document321. CASE для выбора номера из шести таблиц исчезает.
«ВЫРАЗИТЬ» не является отбором строк. Если в «ДокументОснование» хранится ссылка другого типа, выражение вернет NULL для реквизита «Номер», но сама строка останется в результате. Если строки других типов нужно исключить, дополнительно используется оператор «ССЫЛКА».
5. Что показал тест
Условия, в которых был получен результат:
| Параметр | Значение |
|---|---|
| Версия платформы | 1С:Предприятие 8.3 (8.3.27.1859) |
| СУБД | MS SQL Server 2019 |
| Всего записей в таблице документов | 136 907 |
| Период выборки | 3 года |
| Состояние кэша | Кэш прогрет |
| Типов в «ДокументОснование» | 6 |
В зафиксированном сравнении на этой базе исходная реализация выполнялась за 3,8 секунды, после ограничения типа — за 0,5 секунды.
| Показатель | До изменения | После изменения |
|---|---|---|
| Присоединяемых таблиц документов | 6 | 1 |
| Время выполнения | 3,8 с | 0,5 с |
| Разница по времени | — | 3,3 с |
| Отношение времени | — | 7,6 раза |
Цифру 7,6 нельзя переносить на другие запросы как «типичное ускорение от ВЫРАЗИТЬ»: это результат одного замера на одной базе. Гарантированным результатом в рассматриваемом запросе является изменение структуры SQL: шесть соединений вместо одного.
Есть и еще один принципиальный момент: вариант с «ВЫРАЗИТЬ» корректен только потому, что по условиям этой задачи номер требуется для «Документ.ЗаказКлиента», а для других типов ожидается NULL. При оптимизации нужно сохранять не текстовую эквивалентность исходной реализации, а требуемый прикладной результат.
6. Как выбрать конструкцию
У «ССЫЛКА» и «ВЫРАЗИТЬ» разные задачи. Конструкция определяется требуемым результатом: сохранить строки других типов или отфильтровать их.
6.1. Только «ВЫРАЗИТЬ»
Нужно получить реквизит одного конкретного типа, но строки с другими типами должны остаться в результате:
ВЫБРАТЬ
ЗаказПоставщику.Ссылка,
ЗаказПоставщику.ДокументОснование,
ВЫРАЗИТЬ(
ЗаказПоставщику.ДокументОснование
КАК Документ.ЗаказКлиента
).Номер КАК НомерЗаказаКлиента
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
Для оснований других типов «НомерЗаказаКлиента» будет NULL.
6.2. Только «ССЫЛКА»
Нужно оставить только строки определенного типа, но реквизиты связанного объекта не требуются:
ВЫБРАТЬ
ЗаказПоставщику.Ссылка,
ЗаказПоставщику.ДокументОснование
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ГДЕ
ЗаказПоставщику.ДокументОснование
ССЫЛКА Документ.ЗаказКлиента
6.3. «ССЫЛКА» и «ВЫРАЗИТЬ»
Нужно и отобрать конкретный тип, и получить его реквизит:
ВЫБРАТЬ
ЗаказПоставщику.Ссылка,
ВЫРАЗИТЬ(
ЗаказПоставщику.ДокументОснование
КАК Документ.ЗаказКлиента
).Номер КАК НомерЗаказаКлиента
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
ГДЕ
ЗаказПоставщику.ДокументОснование
ССЫЛКА Документ.ЗаказКлиента
«ССЫЛКА» здесь отвечает за отбор строк, а «ВЫРАЗИТЬ» — за получение реквизита из конкретного типа.
6.4. Нужно обработать несколько типов
Если реквизит нужен для нескольких типов, их можно перечислить явно:
ВЫБРАТЬ
ЗаказПоставщику.Ссылка,
ВЫБОР
КОГДА ЗаказПоставщику.ДокументОснование
ССЫЛКА Документ.ЗаказКлиента
ТОГДА ВЫРАЗИТЬ(
ЗаказПоставщику.ДокументОснование
КАК Документ.ЗаказКлиента
).Номер
КОГДА ЗаказПоставщику.ДокументОснование
ССЫЛКА Документ.ЗаказНаПеремещение
ТОГДА ВЫРАЗИТЬ(
ЗаказПоставщику.ДокументОснование
КАК Документ.ЗаказНаПеремещение
).Номер
ИНАЧЕ NULL
КОНЕЦ КАК НомерОснования
ИЗ
Документ.ЗаказПоставщику КАК ЗаказПоставщику
В показанном запросе SQL-трансляция содержит соединения только для двух явно обрабатываемых типов, а не для всего состава ссылки. Оптимизация не должна менять требуемый результат ради красивого SQL.
7. Как найти такую проблему в существующем запросе
Проверять нужно следующие признаки:
- после добавления одного поля вида «Ссылка.Номер» или «Ссылка.Наименование» запрос заметно замедлился;
- в сгенерированном SQL появилась серия LEFT JOIN к однотипным таблицам и большой CASE;
- в фактическом плане есть таблицы, которые не нужны по смыслу конкретной задачи;
- разыменовываемое поле имеет широкий составной или определяемый тип.
Порядок проверки простой: посмотреть SQL, определить реально нужные типы, ограничить их через «ВЫРАЗИТЬ», затем сравнить фактический план и время выполнения. Отдельно нужно проверить, что результат по пустым ссылкам и значениям других типов соответствует бизнес-логике.
8. Когда ничего переписывать не нужно
Не каждое обращение через точку требует оптимизации. Если поле имеет один конкретный ссылочный тип, при разыменовании платформа формирует соединение только с таблицей этого типа. При составном типе соединения формируются по его составу. Наличие нескольких JOIN само по себе не означает заметного замедления: их фактическая стоимость определяется планом выполнения и объемом обрабатываемых данных.
Поэтому правило здесь не «убрать все точки», а «понимать SQL, который формирует платформа». Сначала смотрим SQL и фактический план, затем меняем запрос там, где лишние соединения не требуются прикладной логикой.
Широкий состав реквизита, при котором конкретный механизм использует один-два типа из десятков, — отдельный архитектурный сигнал. Изменение типа реквизита в рабочей системе является самостоятельной задачей; для локальной оптимизации запроса достаточно явно ограничить те типы, реквизиты которых действительно нужны.
Практический чек-лист
- Какой тип у поля: конкретный или составной?
- Какие типы реально нужны в этой задаче?
- Нужно сохранить строки других типов или отфильтровать их?
- Что изменилось в SQL после «ВЫРАЗИТЬ»?
- Что показывает фактический план и время выполнения?
- Сохранился ли требуемый результат для пустых ссылок и других типов?
Заключение
Проблема не в самом разыменовании через точку, а в разыменовании составной ссылки без явного ограничения типа. В рассмотренном примере шесть допустимых типов «ДокументОснование» дали шесть LEFT OUTER JOIN и CASE для выбора номера. После ограничения типа через «ВЫРАЗИТЬ» в SQL осталось одно соединение — с таблицей того документа, который действительно требовался по условиям задачи.
Результат 3,8 с против 0,5 с относится к конкретному тесту и не является универсальным коэффициентом ускорения. Структурный результат однозначен: шесть соединений были заменены одним. Практический вывод: если прикладная логика заранее определяет конкретный тип составной ссылки, этот тип нужно явно сообщить платформе. «ССЫЛКА» используется для отбора по типу, «ВЫРАЗИТЬ» — для получения реквизитов конкретного типа.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.