При написании запросов в платформе 1С:Предприятие разработчики часто сталкиваются с дилеммой: получить реквизит ссылочного поля простым обращением через точку (например, Заказ.ДатаПоступления) или описать явное ЛЕВОЕ СОЕДИНЕНИЕ с таблицей соответствующего объекта. Разберем детально, как эти механизмы работают на уровне СУБД, почему соединение не приводит к медленному перебору всех записей и в каких случаях точка может существенно замедлить работу системы.
Рассмотрим, что происходит, когда мы пишем обращение через точку в тексте запроса. Для платформы 1С ссылочное поле в базе данных представляет собой шестнадцатеричный уникальный идентификатор (GUID). Сами данные объекта (его реквизиты) физически хранятся в отдельной таблице СУБД.
Когда платформа транслирует запрос 1С в реальный SQL-запрос для СУБД (Microsoft SQL Server, PostgreSQL и др.), любое обращение через точку автоматически преобразуется в неявное ЛЕВОЕ СОЕДИНЕНИЕ (LEFT OUTER JOIN). Таким образом, магии не происходит: платформа за разработчика генерирует ту же самую конструкцию соединения с таблицей объекта.
Посмотрим на исходный пример запроса с точкой:
ВЫБРАТЬ
ЗаказыПокупателейОбороты.Заказ.ДатаПоступления КАК ДатаПоступления
ИЗ
РегистрНакопления.ЗаказыПокупателей.Обороты(
,
,
,
Контрагент = &Контрагент
И ДоговорКонтрагента = &ДоговорКонтрагента) КАК ЗаказыПокупателейОбороты
И сравним его с вариантом через явное левое соединение:
ВЫБРАТЬ
ЗаказыПокупателей.ДатаПоступления КАК ДатаПоступления
ИЗ
РегистрНакопления.ЗаказыПокупателей.Обороты(
,
,
,
Контрагент = &Контрагент
И ДоговорКонтрагента = &ДоговорКонтрагента) КАК ЗаказыПокупателейОбороты
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказыПокупателей КАК ЗаказыПокупателей
ПО ЗаказыПокупателейОбороты.Заказ = ЗаказыПокупателей.Ссылка
Если реквизит Заказ имеет однозначный ссылочный тип (ссылается только на документ Документ.ЗаказыПокупателей), то на уровне СУБД оба этих запроса сгенерируют практически идентичный SQL-код.
Распространенное заблуждение начинающих разработчиков заключается в том, что явное соединение заставляет СУБД последовательно перебирать абсолютно все документы в базе, проверяя условие ПО Таблица1.Поле = Таблица2.Ссылка. Выясним причину, почему это не так.
Поле Ссылка у любого справочника, документа или плана видов характеристик является первичным ключом (Primary Key) и по умолчанию имеет кластерный индекс. При выполнении соединения по полю Ссылка СУБД использует быстрый точечный поиск по B-дереву индекса (операция Index Seek), а не полное сканирование таблицы (Table Scan / Index Scan). Это позволяет СУБД находить связанную запись за считанные доли миллисекунды независимо от общего объема документов в базе данных.
Ситуация кардинально меняется, если поле имеет составной тип данных (например, измерение или реквизит может хранить ссылки на несколько видов документов или справочников). Проанализируем, что произойдет в этом случае:
ЛЕВЫЕ СОЕДИНЕНИЯ со всеми таблицами объектов, входящими в составной тип. Если составной тип включает 15 видов документов, запрос превратится в 15 тяжелых соединений, что колоссально увеличит нагрузку на оптимизатор СУБД и память сервера.Если мы хотим оставить обращение через точку к полю составного типа, необходимо обязательно ограничивать тип с помощью конструкции ВЫРАЗИТЬ():
ВЫБРАТЬ
ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.Заказ КАК Документ.ЗаказыПокупателей).ДатаПоступления КАК ДатаПоступления
ИЗ
РегистрНакопления.ЗаказыПокупателей.Обороты(
,
,
,
Контрагент = &Контрагент
И ДоговорКонтрагента = &ДоговорКонтрагента) КАК ЗаказыПокупателейОбороты
Использование ВЫРАЗИТЬ() подсказывает платформе, к какой именно таблице нужно построить соединение, исключая генерацию соединений со всеми остальными объектами метаданных.
Еще одна важная причина предпочесть явное соединение — наложение условий в секции ГДЕ. Рассмотрим разницу:
ГДЕ: Написание условий вида ГДЕ Заказ.Контрагент = &Контрагент генерирует неявное левое соединение, а само условие накладывается на итоговый результат уже после соединения. Это часто мешает СУБД эффективно отфильтровать строки на раннем этапе выполнения плана.ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN), которое автоматически отсекает пустые, незаполненные и битые ссылки, работая в разы быстрее левого соединения. Кроме того, условия фильтрации можно вынести непосредственно в блок ПО (ON), что оптимизирует план запроса.Заказ.Контрагент.ОсновнойБанковскийСчет.НомерСчета создают длинную цепочку вложенных левых соединений, что гарантированно снижает производительность на больших объемах данных.Если в запросе требуется получить множество различных реквизитов из одного и того же связанного объекта по повторяющимся строкам, хорошей практикой является использование временных таблиц:
ВЫБРАТЬ РАЗЛИЧНЫЕ во временную таблицу.ИНДЕКСИРОВАТЬ ПО.Подведем итоги выбора между точкой и соединением:
ЛЕВОМУ СОЕДИНЕНИЮ.ВЫРАЗИТЬ() является грубой ошибкой, приводящей к соединению со всеми таблицами типов.ВНУТРЕННЕЕ СОЕДИНЕНИЕ.