Как правильно и оптимально написать условие с разыменованием в запросе 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки на платформе 1С:Предприятие 8 программисты часто сталкиваются с необходимостью получения данных из реквизитов документов через поле Регистратор (или любое другое поле составного типа). На первый взгляд, задача кажется простой, однако при больших объемах данных неправильный подход к написанию условия в секции ГДЕ может привести к значительным задержкам и деградации производительности всей системы. В этой статье мы подробно разберем, как оптимизатор СУБД реагирует на различные варианты разыменования и как написать запрос, который будет работать максимально быстро.

Анализ проблемы: почему разыменование через точку — это дорого?

Рассмотрим стандартную ситуацию: у нас есть регистр накопления ЗаказыПоставщикам, и нам нужно выбрать записи, где СкладКомпании в документе-регистраторе соответствует определенному значению. Проблема заключается в том, что Регистратор — это составной тип данных. Если мы просто напишем ЗаказыПоставщикам.Регистратор.СкладКомпании, платформа 1С при генерации SQL-запроса создаст LEFT JOIN ко всем таблицам документов, которые могут выступать регистраторами для данного регистра. Если таких документов 50, то в SQL-запросе будет 50 соединений, что крайне негативно скажется на скорости выполнения.

Для решения этой проблемы мы используем оператор ВЫРАЗИТЬ, который ограничивает круг поиска конкретными таблицами. Но как именно вписать это в условие ГДЕ? Рассмотрим предложенные варианты.

Вариант 1: Использование CASE вне оператора сравнения

Проанализируем первый вариант, где конструкция ВЫБОР возвращает значение поля, которое затем сравнивается с параметром:


ВЫБОР
    КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.КорректировкаПоступления
        ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.КорректировкаПоступления).СкладКомпании
    КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.ПоступлениеТоваров
        ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.ПоступлениеТоваров).СкладКомпании
КОНЕЦ = &ТранзСкладКомпании

В этом случае СУБД сначала должна вычислить результат всей функции ВЫБОР для каждой строки, а уже потом сравнить результат с параметром &ТранзСкладКомпании. Это делает условие «не-SARGable» (Search Argumentable), что означает невозможность использования индексов по полю СкладКомпании в таблицах документов. СУБД будет вынуждена выполнять полное сканирование таблиц.

Вариант 2: Сравнение внутри веток CASE

Второй вариант предполагает перенос сравнения внутрь логических веток:


ВЫБОР
    КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.КорректировкаПоступления
        ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.КорректировкаПоступления).СкладКомпании = &ТранзСкладКомпании
    КОГДА ЗаказыПоставщикам.Регистратор ССЫЛКА Документ.ПоступлениеТоваров
        ТОГДА ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.ПоступлениеТоваров).СкладКомпании = &ТранзСкладКомпании
    ИНАЧЕ ЛОЖЬ 
КОНЕЦ

Этот вариант выглядит более наглядным для разработчика, так как мы явно обрабатываем логику. Однако для оптимизатора SQL принципиальной разницы с первым вариантом нет — здесь по-прежнему присутствует сложная логическая конструкция в секции фильтрации, которая мешает эффективному поиску по индексам.

Вариант 3: Использование ОБЪЕДИНИТЬ ВСЕ — золотой стандарт оптимизации

Выясним, какой подход будет самым производительным. Как было отмечено в ходе обсуждения, наиболее эффективным решением является разделение запроса на части с последующим объединением результатов. Разберем этот метод по шагам:

  1. Создаем первый подзапрос, который работает только с типом КорректировкаПоступления.
  2. Создаем второй подзапрос для типа ПоступлениеТоваров.
  3. Объединяем их через ОБЪЕДИНИТЬ ВСЕ.

Пример кода:


ВЫБРАТЬ
    ЗаказыПоставщикам.Ссылка
ИЗ
    РегистрНакопления.ЗаказыПоставщикам КАК ЗаказыПоставщикам
ГДЕ
    ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.КорректировкаПоступления).СкладКомпании = &ТранзСкладКомпании

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ЗаказыПоставщикам.Ссылка
ИЗ
    РегистрНакопления.ЗаказыПоставщикам КАК ЗаказыПоставщикам
ГДЕ
    ВЫРАЗИТЬ(ЗаказыПоставщикам.Регистратор КАК Документ.ПоступлениеТоваров).СкладКомпании = &ТранзСкладКомпании

Почему это работает быстрее? В каждом из подзапросов СУБД видит прямое соединение с конкретной таблицей документа. Условие СкладКомпании = &ТранзСкладКомпании становится простым и понятным фильтром. В этом случае SQL Server или PostgreSQL могут выполнить Index Seek — быстрый поиск по индексу в таблице документа, что в десятки и сотни раз быстрее, чем перебор всех записей в CASE.

Нюансы обработки NULL

Важно проанализировать, что произойдет, если Регистратор имеет тип, не указанный в наших условиях. Оператор ВЫРАЗИТЬ(Регистратор КАК Документ.Тип) для несовпадающего типа вернет значение NULL.

В первом варианте сравнение NULL = &Параметр даст результат Unknown, и такая строка будет отсеяна. Во втором варианте мы явно указали ИНАЧЕ ЛОЖЬ, что делает поведение запроса предсказуемым. Однако при использовании ОБЪЕДИНИТЬ ВСЕ проблема решается сама собой: записи с неподходящими типами просто не попадут ни в один из подзапросов, так как NULL никогда не будет равен значению склада в параметре.

Рекомендации по применению

Посмотрим на общие правила, которые помогут вам писать быстрые запросы:

  1. Ограничивайте типы: Всегда используйте ВЫРАЗИТЬ при обращении к полям составного типа через точку. Это избавляет запрос от лишних LEFT JOIN.
  2. Избегайте ВЫБОР в ГДЕ: Если вам нужно фильтровать данные по реквизитам разных типов документов, лучше использовать ОБЪЕДИНИТЬ ВСЕ.
  3. Используйте временные таблицы: Если в запросе много соединений, попробуйте сначала отобрать нужные Регистраторы во временную таблицу, а затем соединять её с основной выборкой.
  4. Проверяйте виртуальные таблицы: Помните, что фильтрация по реквизитам регистратора в виртуальных таблицах (Остатки, Обороты) в секции ГДЕ крайне неэффективна. Старайтесь выносить такие фильтры на уровень измерений, если это возможно.

Проанализировав ситуацию, можно сделать вывод: хотя использование ВЫБОР в запросе кажется более компактным, для высоконагруженных систем вариант с ОБЪЕДИНИТЬ ВСЕ является единственно верным с точки зрения производительности СУБД. Это позволяет оптимизатору построить наиболее эффективный план выполнения и задействовать имеющиеся индексы.

← На главную