В процессе разработки на платформе 1С:Предприятие 8 программисты часто сталкиваются с необходимостью получения данных из реквизитов документов через поле Регистратор (или любое другое поле составного типа). На первый взгляд, задача кажется простой, однако при больших объемах данных неправильный подход к написанию условия в секции ГДЕ может привести к значительным задержкам и деградации производительности всей системы. В этой статье мы подробно разберем, как оптимизатор СУБД реагирует на различные варианты разыменования и как написать запрос, который будет работать максимально быстро.
Рассмотрим стандартную ситуацию: у нас есть регистр накопления ЗаказыПоставщикам, и нам нужно выбрать записи, где СкладКомпании в документе-регистраторе соответствует определенному значению. Проблема заключается в том, что Регистратор — это составной тип данных. Если мы просто напишем ЗаказыПоставщикам.Регистратор.СкладКомпании, платформа 1С при генерации SQL-запроса создаст LEFT JOIN ко всем таблицам документов, которые могут выступать регистраторами для данного регистра. Если таких документов 50, то в SQL-запросе будет 50 соединений, что крайне негативно скажется на скорости выполнения.
Для решения этой проблемы мы используем оператор ВЫРАЗИТЬ, который ограничивает круг поиска конкретными таблицами. Но как именно вписать это в условие ГДЕ? Рассмотрим предложенные варианты.
Проанализируем первый вариант, где конструкция ВЫБОР возвращает значение поля, которое затем сравнивается с параметром:
ВЫБОР
КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.КорректировкаПоступления
ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.КорректировкаПоступления).СкладКомпании
КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.ПоступлениеТоваров
ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.ПоступлениеТоваров).СкладКомпании
КОНЕЦ = &ТранзСкладКомпании
В этом случае СУБД сначала должна вычислить результат всей функции ВЫБОР для каждой строки, а уже потом сравнить результат с параметром &ТранзСкладКомпании. Это делает условие «не-SARGable» (Search Argumentable), что означает невозможность использования индексов по полю СкладКомпании в таблицах документов. СУБД будет вынуждена выполнять полное сканирование таблиц.
Второй вариант предполагает перенос сравнения внутрь логических веток:
ВЫБОР
КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.КорректировкаПоступления
ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.КорректировкаПоступления).СкладКомпании = &ТранзСкладКомпании
КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.ПоступлениеТоваров
ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.ПоступлениеТоваров).СкладКомпании = &ТранзСкладКомпании
ИНАЧЕ ЛОЖЬ
КОНЕЦ
Этот вариант выглядит более наглядным для разработчика, так как мы явно обрабатываем логику. Однако для оптимизатора SQL принципиальной разницы с первым вариантом нет — здесь по-прежнему присутствует сложная логическая конструкция в секции фильтрации, которая мешает эффективному поиску по индексам.
Выясним, какой подход будет самым производительным. Как было отмечено в ходе обсуждения, наиболее эффективным решением является разделение запроса на части с последующим объединением результатов. Разберем этот метод по шагам:
КорректировкаПоступления.ПоступлениеТоваров.ОБЪЕДИНИТЬ ВСЕ.Пример кода:
ВЫБРАТЬ
ЗаказыПоставщикам.Ссылка
ИЗ
РегистрНакопления.ЗаказыПоставщикам КАК ЗаказыПоставщикам
ГДЕ
ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.КорректировкаПоступления).СкладКомпании = &ТранзСкладКомпании
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЗаказыПоставщикам.Ссылка
ИЗ
РегистрНакопления.ЗаказыПоставщикам КАК ЗаказыПоставщикам
ГДЕ
ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.ПоступлениеТоваров).СкладКомпании = &ТранзСкладКомпании
Почему это работает быстрее? В каждом из подзапросов СУБД видит прямое соединение с конкретной таблицей документа. Условие СкладКомпании = &ТранзСкладКомпании становится простым и понятным фильтром. В этом случае SQL Server или PostgreSQL могут выполнить Index Seek — быстрый поиск по индексу в таблице документа, что в десятки и сотни раз быстрее, чем перебор всех записей в CASE.
Важно проанализировать, что произойдет, если Регистратор имеет тип, не указанный в наших условиях. Оператор ВЫРАЗИТЬ(Регистратор КАК Документ.Тип) для несовпадающего типа вернет значение NULL.
В первом варианте сравнение NULL = &Параметр даст результат Unknown, и такая строка будет отсеяна. Во втором варианте мы явно указали ИНАЧЕ ЛОЖЬ, что делает поведение запроса предсказуемым. Однако при использовании ОБЪЕДИНИТЬ ВСЕ проблема решается сама собой: записи с неподходящими типами просто не попадут ни в один из подзапросов, так как NULL никогда не будет равен значению склада в параметре.
Посмотрим на общие правила, которые помогут вам писать быстрые запросы:
ВЫРАЗИТЬ при обращении к полям составного типа через точку. Это избавляет запрос от лишних LEFT JOIN.ОБЪЕДИНИТЬ ВСЕ.Регистраторы во временную таблицу, а затем соединять её с основной выборкой.ГДЕ крайне неэффективна. Старайтесь выносить такие фильтры на уровень измерений, если это возможно.Проанализировав ситуацию, можно сделать вывод: хотя использование ВЫБОР в запросе кажется более компактным, для высоконагруженных систем вариант с ОБЪЕДИНИТЬ ВСЕ является единственно верным с точки зрения производительности СУБД. Это позволяет оптимизатору построить наиболее эффективный план выполнения и задействовать имеющиеся индексы.