При написании запросов в платформе 1С:Предприятие разработчики часто используют конструкцию вида Документ.Контрагент.ИНН. Начинающие программисты опасаются использовать обращения через несколько точек, считая это абсолютной ошибкой оптимизации. В этой статье мы подробно разберем, как платформа 1С и СУБД обрабатывают такие обращения, в каких случаях обращение «через точку» безопасно, а когда оно способно критически замедлить работу системы.
Рассмотрим, что происходит на физическом уровне при размоноличивании полей. Когда мы обращаемся к реквизиту единичного ссылочного типа (например, поле Контрагент с типом СправочникСсылка.Контрагенты), платформа 1С автоматически добавляет неявное левое соединение (LEFT OUTER JOIN) с таблицей данного справочника.
Проанализируем простой пример запроса:
ВЫБРАТЬ
Реализация.Контрагент.ИНН КАК КонтрагентИНН
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Ссылка = &Ссылка
Совсем иначе ситуация выглядит, если реквизит имеет составной тип данных (например, поле Регистратор в регистрах накопления или поле Владелец). В этом случае, если обратиться к свойству составного поля через точку без явного приведения типа, платформа вынуждена сформировать неявное левое соединение со всеми возможными таблицами, входящими в этот составной тип.
Посмотрим на трансляцию запросов платформой 1С на уровень SQL-сервера. Разберем сначала простой ссылочный тип. Для первого примера СУБД сгенерирует следующий лаконичный SQL-код:
SELECT
T2._Code AS ИНН
FROM
_Document234 T1
LEFT OUTER JOIN
_Reference115 T2 ON T1._Fld4520 = T2._IDRRef
WHERE
T1._IDRRef = 0xBFBECFADC0E8B42A11E4F2F476A97D5D
План выполнения такого запроса содержит простой NESTED OUTER LOOP с точечным поиском по индексу, что работает максимально быстро и эффективно.
Теперь проанализируем пример с составным типом без приведения типа:
ВЫБРАТЬ
ТоварыОбороты.Регистратор.Контрагент.ИНН КАК ИНН
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(&НачДата, &КонДата, Регистратор) КАК ТоварыОбороты
Поскольку регистратором могут выступать десятки различных документов, платформа сгенерирует громоздкий SQL-запрос с массой соединений и условным оператором CASE WHEN:
SELECT
CASE
WHEN T1.RecorderTRef = 0x0000009D THEN T4._Fld1200
WHEN T1.RecorderTRef = 0x0000002D THEN T6._Fld1200
WHEN T1.RecorderTRef = 0x00000057 THEN T8._Fld1200
ELSE CAST(NULL AS NVARCHAR(12))
END
FROM T1
LEFT OUTER JOIN Документ_СчетНаОплату T3 ON T1.RecorderTRef = 0x0000009D AND T1.RecorderRRef = T3._IDRRef
LEFT OUTER JOIN Справочник_Контрагенты T4 ON T3._Fld500 = T4._IDRRef
LEFT OUTER JOIN Документ_ПоступлениеТоваров T5 ON T1.RecorderTRef = 0x0000002D AND T1.RecorderRRef = T5._IDRRef
LEFT OUTER JOIN Справочник_Контрагенты T6 ON T5._Fld500 = T6._IDRRef
LEFT OUTER JOIN Документ_РеализацияТоваров T7 ON T1.RecorderTRef = 0x00000057 AND T1.RecorderRRef = T7._IDRRef
LEFT OUTER JOIN Справочник_Контрагенты T8 ON T7._Fld500 = T8._IDRRef
Такой запрос заставляет СУБД считывать данные из всех таблиц документов, существующих в конфигурации для данного регистра. Это приводит к выполнению лишних сканирований таблиц (Table Scan / Index Scan), колоссальному расходу оперативной памяти и задержкам при выполнении.
Чтобы избежать соединения со всеми таблицами составного типа, необходимо жестко ограничить тип данных с помощью функции ВЫРАЗИТЬ. Выясним, как правильно изменить запрос:
ВЫБРАТЬ
ВЫРАЗИТЬ(ТоварыОбороты.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент.ИНН КАК ИНН
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(&НачДата, &КонДата, Регистратор) КАК ТоварыОбороты
Благодаря явному приведению типа платформа свяжет таблицу оборотов только с одной физической таблицей документа РеализацияТоваровУслуг и справочника Контрагенты, полностью исключив остальные варианты.
Важно: Применение функции ВЫРАЗИТЬ требуется не только в секции ВЫБРАТЬ, но и в секциях ГДЕ, ПО, ГРУППИРОВАТЬ ПО и УПОРЯДОЧИТЬ ПО. Если обратиться к составному полю через точку в условии отбора без приведения типа, СУБД сгенерирует соединение со всеми таблицами еще до наложения фильтра.
Разберем еще несколько критических сценариев, о которых важно помнить при разработке систем на 1С:
ОстаткиИОбороты или СрезПоследних) является прямым нарушением стандартов 1С. Платформа не сможет применить такой отбор при первичной выборке, из-за чего СУБД сформирует всю таблицу целиком и только затем наложит фильтр.ГДЕ (например, ГДЕ Договор.Контрагент.ИНН = &ИНН), оптимизатор СУБД превращает неявное LEFT JOIN в INNER JOIN. Если ключевые поля не проиндексированы, порядок соединения таблиц может стать неоптимальным.Договор.ФизЛицо.ФизическоеЛицо.Наименование создает новую таблицу в SQL-запросе. При глубокой вложенности оптимизатор СУБД тратит на компиляцию и построение плана выполнения больше времени, чем на сам поиск данных.Рассмотрим реальную задачу. Имеется документ с реквизитами организации, контрагента, партнера и физических лиц, из которых требуется извлечь контактную информацию и реквизиты. Исходный запрос содержит множество обращений через 2–3 точки к связанным справочникам.
Проанализируем оптимальный подход: вместо раздувания запроса неявными соединениями мы выгружаем ключевые ссылки во временную таблицу и затем выполняем явные левые соединения.
Рассмотрим пример формирования оптимизированного запроса:
// 1. Помещаем исходные параметры во временную таблицу
ВЫБРАТЬ
Договор.Контрагент КАК Контрагент,
Договор.Партнер КАК Партнер,
Договор.Организация КАК Организация
ПОМЕСТИТЬ ВТ_ДанныеДокумента
ИЗ
Документ.ДоговорСтроительноМонтажныхРабот КАК Договор
ГДЕ
Договор.Ссылка = &Ссылка;
////////////////////////////////////////////////////////////////////////////////
// 2. Извлекаем контактную информацию контрагента и партнера через объединение
ВЫБРАТЬ
Контакты.Представление КАК Представление,
Контакты.Вид.Наименование КАК Вид
ПОМЕСТИТЬ ВТ_Контакты
ИЗ
ВТ_ДанныеДокумента КАК ДанныеДокумента
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты.КонтактнаяИнформация КАК Контакты
ПО ДанныеДокумента.Контрагент = Контакты.Ссылка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Контакты.Представление,
Контакты.Вид.Наименование
ИЗ
ВТ_ДанныеДокумента КАК ДанныеДокумента
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Партнеры.КонтактнаяИнформация КАК Контакты
ПО ДанныеДокумента.Партнер = Контакты.Ссылка;
////////////////////////////////////////////////////////////////////////////////
// 3. Выбираем итоговый результат
ВЫБРАТЬ РАЗЛИЧНЫЕ
Контакты.Представление КАК Представление,
Контакты.Вид КАК Вид
ИЗ
ВТ_Контакты КАК Контакты
Подведем итоги: обращаться через точку к простым ссылочным типам в секции ВЫБРАТЬ можно без опасений — платформа построит обычное соединение. Однако при работе с составными типами обязателен оператор ВЫРАЗИТЬ, а в сложных выборках с глубокой вложенностью всегда предпочтительнее использовать временные таблицы и явные соединения.