В чем разница между обычными и вложенными левыми соединениями в запросах 1С?

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

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

Суть вопроса: синтаксическая разница

Рассмотрим два варианта написания одного и того же (по составу таблиц) запроса. Проанализируем ситуацию, когда у нас есть основная таблица (например, Документ1) и две справочные таблицы, которые мы хотим к ней присоединить.

Вариант 1: Последовательное соединение

В этом случае таблицы присоединяются одна за другой, «плоским» списком:


ВЫБРАТЬ
    Документ1.Ссылка,
    ВложенныйЗапрос1.Ссылка КАК Ссылка1,
    ВложенныйЗапрос2.Ссылка КАК Ссылка2
ИЗ
    Документ.Документ1 КАК Документ1
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Справочник1 КАК ВложенныйЗапрос1
        ПО Документ1.Реквизит1 = ВложенныйЗапрос1.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Справочник1 КАК ВложенныйЗапрос2
        ПО Документ1.Реквизит2 = ВложенныйЗапрос2.Ссылка

Вариант 2: Вложенное соединение

Здесь структура иная — мы как бы соединяем вторую и третью таблицу между собой, а затем результат этого объединения присоединяем к первой таблице. В коде это выглядит как «лесенка» из ключевых слов ПО в конце запроса:


ВЫБРАТЬ
    Документ1.Ссылка,
    ВложенныйЗапрос1.Ссылка КАК Ссылка1,
    ВложенныйЗапрос2.Ссылка КАК Ссылка2
ИЗ
    Документ.Документ1 КАК Документ1
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Справочник1 КАК ВложенныйЗапрос1
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Справочник1 КАК ВложенныйЗапрос2
        ПО ВложенныйЗапрос1.РеквизитХ = ВложенныйЗапрос2.Ссылка
    ПО Документ1.Реквизит1 = ВложенныйЗапрос1.Ссылка

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

Разберем логические отличия и работу с NULL

Выясним причину, почему результат этих запросов может быть разным. В последовательном соединении (Вариант 1) каждая таблица ЛЕВОЕ СОЕДИНЕНИЕ независима в контексте связи с основной таблицей. Если для Документ1 не найдется записи в Справочник1, поля первой присоединенной таблицы будут NULL, но это никак не помешает платформе попытаться найти данные во второй присоединенной таблице.

Во вложенном соединении (Вариант 2) логика иная. Сначала формируется результат соединения правой части. Если мы используем конструкцию A LEFT JOIN (B LEFT JOIN C), то СУБД сначала пытается соединить B и C. Если в таблице B нет записи, соответствующей условию связи с A, то вся группа (B + C) для данной строки A будет содержать NULL. Это может привести к потере данных из таблицы C, которые могли бы быть получены при последовательном соединении.

Проанализируем ситуацию на примере арифметики, как это предлагалось в обсуждении. Если A * B * C — это последовательные действия, то A * (B * C) — это группировка. В SQL-запросах такая группировка заставляет оптимизатор СУБД строить план запроса определенным образом, зачастую менее эффективным.

Влияние на производительность и оптимизатор СУБД

Рассмотрим подробнее, что происходит «под капотом» системы. Когда платформа 1С транслирует запрос в язык SQL (для MS SQL Server, PostgreSQL и др.), вложенные соединения часто превращаются в неявные подзапросы.

  1. Барьер оптимизации: Для СУБД вложенное соединение является жестким указанием порядка действий. Оптимизатор не всегда может «развернуть» такую конструкцию в плоский план. В итоге вместо использования эффективных индексов по всем таблицам сразу, сервер может сначала физически выполнить соединение внутренних таблиц во временной памяти, а уже потом соединять их с основной выборкой.
  2. Оценка кардинальности: СУБД сложнее предсказать количество строк, которое вернет вложенный блок. Это приводит к ошибкам в выборе алгоритма соединения (например, использование Nested Loops там, где уместнее Hash Join), что на больших объемах данных (миллионы строк) приводит к катастрофическому замедлению работы.
  3. Виртуальные таблицы: Проблема усугубляется, если внутри вложенного соединения используются виртуальные таблицы (Остатки, Срезы Последних). Это создает «тяжелые» подзапросы, которые могут полностью блокировать эффективную работу сервера.

Стандарты разработки 1С и рекомендация #std655

Важно помнить, что фирма 1С прямо запрещает использование вложенных соединений в большинстве случаев. Согласно стандарту #std655, следует избегать неявных подзапросов, возникающих при вложенных соединениях.

Рассмотрим рекомендацию стандарта по шагам:

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

Посмотрим на пример из стандарта, который иллюстрирует ошибочный подход:


// ТАК ДЕЛАТЬ НЕ РЕКОМЕНДУЕТСЯ
ВЫБРАТЬ ...
ИЗ Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах КАК Товары
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыОрганизаций КАК ТоварыОрг
    ПО Товары.Склад = ТоварыОрг.Склад
ПО Номенклатура.Ссылка = Товары.Номенклатура

Этот запрос следует переписать либо в последовательное соединение (если логика позволяет), либо вынести соединение регистров во временную таблицу ВТ_ДанныеПоСкладам, а затем соединить Справочник.Номенклатура с этой временной таблицей.

Как не допустить вложенных соединений в Конструкторе запросов

Зачастую вложенные соединения появляются в коде случайно из-за особенностей работы Конструктора запросов 1С. Посмотрим, как это происходит:

Когда вы находитесь на закладке «Связи», обратите внимание на порядок строк. Если вы добавляете связи последовательно (Таблица1 с Таблица2, Таблица1 с Таблица3), конструктор создает плоскую структуру. Но если вы сначала свяжете Таблицу2 с Таблицей3, а затем попытаетесь «привязать» их к Таблице1, конструктор может создать «лесенку» (вложенность).

Практический совет: всегда проверяйте визуальное отображение связей. В колонках «Таблица 1» и «Таблица 2» левой таблицей всегда должна выступать либо самая первая (основная) таблица, либо таблица, которая уже была задействована в предыдущих связях выше по списку. Если вы видите, что флажки связей смещаются вправо — это сигнал о создании вложенного соединения.

Резюме

Проанализировав ситуацию, мы можем сделать однозначный вывод: вложенные соединения — это механизм, которого следует избегать в 99% случаев. Они делают код менее читаемым, нарушают стандарты 1С и, что самое главное, создают непредсказуемые нагрузки на сервер базы данных.

Основные правила для закрепления:

← На главную