В практике разработки на платформе 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.Ссылка
Главное визуальное отличие — во втором варианте операторы ПО идут подряд в конце блока ИЗ, что указывает на иерархическую структуру связей.
Выясним причину, почему результат этих запросов может быть разным. В последовательном соединении (Вариант 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 и др.), вложенные соединения часто превращаются в неявные подзапросы.
Nested Loops там, где уместнее Hash Join), что на больших объемах данных (миллионы строк) приводит к катастрофическому замедлению работы.Важно помнить, что фирма 1С прямо запрещает использование вложенных соединений в большинстве случаев. Согласно стандарту #std655, следует избегать неявных подзапросов, возникающих при вложенных соединениях.
Рассмотрим рекомендацию стандарта по шагам:
Посмотрим на пример из стандарта, который иллюстрирует ошибочный подход:
// ТАК ДЕЛАТЬ НЕ РЕКОМЕНДУЕТСЯ
ВЫБРАТЬ ...
ИЗ Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыОрганизаций КАК ТоварыОрг
ПО Товары.Склад = ТоварыОрг.Склад
ПО Номенклатура.Ссылка = Товары.Номенклатура
Этот запрос следует переписать либо в последовательное соединение (если логика позволяет), либо вынести соединение регистров во временную таблицу ВТ_ДанныеПоСкладам, а затем соединить Справочник.Номенклатура с этой временной таблицей.
Зачастую вложенные соединения появляются в коде случайно из-за особенностей работы Конструктора запросов 1С. Посмотрим, как это происходит:
Когда вы находитесь на закладке «Связи», обратите внимание на порядок строк. Если вы добавляете связи последовательно (Таблица1 с Таблица2, Таблица1 с Таблица3), конструктор создает плоскую структуру. Но если вы сначала свяжете Таблицу2 с Таблицей3, а затем попытаетесь «привязать» их к Таблице1, конструктор может создать «лесенку» (вложенность).
Практический совет: всегда проверяйте визуальное отображение связей. В колонках «Таблица 1» и «Таблица 2» левой таблицей всегда должна выступать либо самая первая (основная) таблица, либо таблица, которая уже была задействована в предыдущих связях выше по списку. Если вы видите, что флажки связей смещаются вправо — это сигнал о создании вложенного соединения.
Проанализировав ситуацию, мы можем сделать однозначный вывод: вложенные соединения — это механизм, которого следует избегать в 99% случаев. Они делают код менее читаемым, нарушают стандарты 1С и, что самое главное, создают непредсказуемые нагрузки на сервер базы данных.
Основные правила для закрепления:
ПО и работу с NULL.