Как секция ГДЕ влияет на соединение таблиц в запросах 1С: логика и работа оптимизатора СУБД

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

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

Логический порядок выполнения запроса против физического плана

Для понимания механизма работы запросов важно четко разделять два понятия: стандартную логическую модель реляционной алгебры и физический план выполнения, который формирует оптимизатор СУБД.

С точки зрения реляционной логики языка SQL и платформы 1С порядок обработки следующий:

  1. Сначала вычисляется декартово произведение или соединение источников данных в секции ИЗ по предикатам секции ПО.
  2. В случае ЛЕВОЕ СОЕДИНЕНИЕ для строк левой таблицы, не нашедших соответствия в правой таблице, формируются поля со значениями NULL.
  3. Только затем к полученному общему набору данных применяется фильтрация секции ГДЕ.

Однако реляционная СУБД (например, MS SQL Server или PostgreSQL) никогда не выполняет запросы буквально «в лоб», если это приводит к неэффективной работе с памятью и диском. Главная задача оптимизатора запросов — выдать точный результат, затратив минимум ресурсов. Если изменение последовательности операций не влияет на итоговый результат выборки, оптимизатор имеет полное право поменять порядок действий: сначала отфильтровать строки по индексу (Index Seek), а затем выполнить соединение (Nested Loops, Hash Match или Merge Join).

Анализ практического примера: транзитивность и превращение левого соединения во внутреннее

Рассмотрим запрос, вызвавший споры в сообществе разработчиков:


ВЫБРАТЬ
    Т1.Рес1,
    Т2.Рес1
ИЗ
    РегистрСведений.хх КАК Т1
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.хх КАК Т2
        ПО Т1.Изм1 = Т2.Изм1
        И Т1.Изм2 = Т2.Изм2
ГДЕ
    Т2.Изм2 = "блабла"

Проанализируем, почему профайлер показывает предварительную выборку строк с поиском по индексу для обеих таблиц, хотя разработчик указал левое соединение и отбор в секции ГДЕ.

В данном запросе срабатывают сразу два механизма оптимизации СУБД:

  1. Упрощение внешнего соединения (Outer Join Simplification): Предикат Т2.Изм2 = "блабла" находится в секции ГДЕ. Любая строка левой таблицы, для которой в правой таблице нет пары, вернет в поле Т2.Изм2 значение NULL. Так как сравнение NULL = "блабла" дает ложь, эти строки гарантированно будут отсечены. Оптимизатор СУБД понимает, что левое соединение в таком виде полностью эквивалентно ВНУТРЕННЕЕ СОЕДИНЕНИЕ, и неявно преобразует его на этапе компиляции плана.
  2. Транзитивное замыкание предикатов (Predicate Transitivity): Поскольку соединение стало внутренним и в секции ПО присутствует строгое равенство Т1.Изм2 = Т2.Изм2, а в ГДЕ задано Т2.Изм2 = "блабла", оптимизатор автоматически генерирует дополнительное условие Т1.Изм2 = "блабла". В итоге СУБД производит индексный поиск (Index Seek) по обеим таблицам еще до их объединения.

Таким образом, логически условие ГДЕ относится ко всей выборке, но физически СУБД применила его к обоим источникам до соединения ради многократного ускорения операции.

В чем разница между размещением условий в секциях «ПО» и «ГДЕ»

Поведение запроса кардинально меняется, если перенести фильтрацию правой таблицы в условие соединения:


ВЫБРАТЬ
    Т1.Рес1,
    Т2.Рес1
ИЗ
    РегистрСведений.хх КАК Т1
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.хх КАК Т2
        ПО Т1.Изм1 = Т2.Изм1
        И Т1.Изм2 = Т2.Изм2
        И Т2.Изм2 = "блабла"

Разберем различия в результатах выполнения:

  1. Фильтрация в секции ПО: условие на правую таблицу фильтрует только присоединяемые данные. Если строка правой таблицы не соответствует значению "блабла", строка левой таблицы не удаляется из результата — поля правой таблицы просто заполняются значениями NULL.
  2. Фильтрация в секции ГДЕ: жесткое условие на равенство или неравенство поля правой таблицы гарантированно отсекает всю строку целиком, превращая левое соединение во внутреннее.
  3. Условие на левую таблицу в секции ПО: если в левом соединении поместить условие на левую таблицу (например, ПО Т1.Изм2 = "блабла"), строки левой таблицы все равно останутся в выборке, но для неподошедших строк поля правой таблицы станут NULL. Фильтровать левую таблицу необходимо исключительно в секции ГДЕ.

Особый случай: проверка на ЕСТЬ NULL (Anti-Join)

Посмотрим на сценарий, когда физический порядок выполнения строго следует логическому. Единственным корректным поводом ставить условие на правую таблицу левого соединения в секции ГДЕ является поиск отсутствующих записей:


ВЫБРАТЬ
    Т1.Изм1,
    Т1.Рес1
ИЗ
    РегистрСведений.хх КАК Т1
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.хх КАК Т2
        ПО Т1.Изм1 = Т2.Изм1
ГДЕ
    Т2.Изм1 ЕСТЬ NULL

В этом случае проверка ЕСТЬ NULL не отвергает отсутствующие строки, а, напротив, оставляет только их. Оптимизатор СУБД не может заменить соединение на внутреннее и выполняет антисоединение (Anti Semi Join), отсекая строки именно на этапе проверки результата слияния.

Специфика платформы 1С и стандарты разработки

При написании запросов в 1С необходимо учитывать технические ограничения платформы и правила разработки:

  1. Явное указание типа соединения: Никогда не следует маскировать внутреннее соединение под левое с условием в ГДЕ. Если в логике требуется обязательное наличие записей в обеих таблицах, всегда пишите ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Это предотвращает ошибки оптимизатора при расчете кардинальности и статистики в сложных запросах с множеством таблиц.
  2. Запрет подзапросов в условии связи: Язык запросов 1С синтаксически не поддерживает вложенные подзапросы внутри секции ПО. Если для соединения требуется сложная фильтрация правой таблицы, предварительно выгружайте данные во временную таблицу.
  3. Использование временных таблиц: Для гарантированной производительности при работе с большими наборами данных отбирайте нужные строки во временную таблицу через объект МенеджерВременныхТаблиц с явным указанием ИНДЕКСИРОВАТЬ ПО. Это исключает непредсказуемое поведение оптимизатора.
  4. Виртуальные таблицы регистров: При обращении к виртуальным таблицам (например, срезам или остаткам) условия необходимо передавать строго в параметры виртуальной таблицы, а не в секции ПО или ГДЕ, иначе СУБД сначала рассчитает итоги по всей базе данных, что приведет к резкому падению производительности.
  5. Влияние механизма RLS: Ограничения на уровне записей разворачиваются платформой 1С в условия секции ГДЕ. Если для правой таблицы левого соединения действуют правила RLS без проверки на NULL, платформенный код может непреднамеренно преобразовать левое соединение во внутреннее, скрыв часть данных левой таблицы.
← На главную