Должен ли я вкладывать зависимые внешние объединения в SQL Server?

9

Я слышал смешанную информацию по этому поводу и надеюсь на каноническое или экспертное мнение.

Если у меня есть несколько LEFT OUTER JOINs, каждый из которых зависит от последнего, лучше ли их вкладывать?

Для надуманного примера, JOINto MyParentзависит от JOINto MyChild: http://sqlfiddle.com/#!3/31022/5

SELECT
    {columns}
FROM
    MyGrandChild AS gc
LEFT OUTER JOIN
    MyChild AS c
        ON c.[Id] = gc.[ParentId]
LEFT OUTER JOIN
    MyParent AS p
        ON p.[id] = c.[ParentId]

введите описание изображения здесь

По сравнению с http://sqlfiddle.com/#!3/31022/7

SELECT
    {columns}
FROM
    MyGrandChild AS gc
LEFT OUTER JOIN
    (
    MyChild AS c            
    LEFT OUTER JOIN
        MyParent AS p
            ON p.[id] = c.[ParentId]
    )
    ON c.[Id] = gc.[ParentId]

введите описание изображения здесь

Как показано выше, они производят разные планы запросов в SS2k8

Мэтью
источник
Мне нравится использовать вложенные объединения: michaeljswart.com/2012/09/when-i-use-nested-joins Хотя это может быть вопросом стиля.
Майкл Дж Сварт
@MichaelJSwart ваш блог появляется только обсудить , когда зависимое JOINэтоINNER JOIN
Matthew
1
Как вы хотите определить «лучше»? Лично я нахожу первое намного легче для чтения - мой ум не колеблется вокруг попыток перепроектировать отношения. Наличие ON ... ONдвух подряд (в скобках или нет) очень сбивает с толку.
Аарон Бертран
4
Когда я не вижу разницы в производительности между двумя способами сделать что-то, следующий вопрос, который я задаю себе: если я попаду в автобус или выиграю в лотерею сегодня вечером, какую версию будет легче понять и поддержать тому, кто завтра примет мой код ?
Аарон Бертран
1
use planНамек работает при пересадке второго плана запроса на первый , но не наоборот.
Мартин Смит

Ответы:

3

Это абсолютно не канонический ответ, но я заметил, что для планов запросов с вложенными циклами, показанных в скрипте SQL, можно было применить план из Query 2 к Query 1 с использованием USE PLANподсказки, но попытка выполнить обратную операцию не удалась с помощью

Обработчику запросов не удалось создать план запроса, поскольку подсказка USE PLAN содержит план, который не может быть проверен как законный для запроса. Удалите или замените подсказку USE PLAN. Чтобы повысить вероятность успешного форсирования плана, убедитесь, что план, предоставленный в подсказке USE PLAN, автоматически генерируется SQL Server для того же запроса.

Отключение правила преобразования оптимизатора также ReorderLOJN предотвращает успешное выполнение ранее выполненной подсказки плана.

Эксперименты с большим количеством данных показывают, что SQL Server, безусловно, способен преобразовываться (A LOJ B) LOJ Cв A LOJ (B LOJ C)естественные условия, но я не видел никаких доказательств того, что обратное верно.

Очень надуманный случай, когда первый запрос работает лучше, чем второй

DROP TABLE  MyGrandChild , MyChild,  MyParent

CREATE TABLE MyParent
(Id int)

CREATE TABLE MyChild
(Id int PRIMARY KEY
,ParentId int,
Filler char(8000) NULL)

CREATE TABLE MyGrandChild
(Id int
,ParentId int)

INSERT INTO MyChild
                      (Id, ParentId)
SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY @@SPID),
                     ROW_NUMBER() OVER (ORDER BY @@SPID)    
FROM master..spt_values  v1, master..spt_values                  

INSERT INTO MyGrandChild
                      (Id, ParentId)
OUTPUT INSERTED.Id INTO MyParent
SELECT TOP (3000) Id, Id AS ParentId
FROM MyChild
ORDER BY Id

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT gc.Id       AS gcId,
       gc.ParentId AS gcpId,
       c.Id        AS cId,
       c.ParentId  AS cpId,
       p.Id        AS pId
FROM   MyGrandChild AS gc
       LEFT OUTER JOIN MyChild AS c
         ON c.[Id] = gc.[ParentId]
       LEFT OUTER JOIN MyParent AS p
         ON p.[Id] = c.[ParentId]

SELECT gc.Id       AS gcId,
       gc.ParentId AS gcpId,
       c.Id        AS cId,
       c.ParentId  AS cpId,
       p.Id        AS pId
FROM   MyGrandChild AS gc
       LEFT OUTER JOIN( MyChild AS c
                        LEFT OUTER JOIN MyParent AS p
                          ON p.[Id] = c.[ParentId])
         ON c.[Id] = gc.[ParentId] 

Который дает планы

введите описание изображения здесь

Для меня Query 1 имел истекшее время 108 мс против 1163 мс для Query 2.

Запрос 1

Table 'Worktable'. Scan count 0, logical reads 0 
Table 'MyChild'. Scan count 0, logical reads 9196
Table 'MyGrandChild'. Scan count 1, logical reads 7
Table 'MyParent'. Scan count 1, logical reads 5

Запрос 2

Table 'MyParent'. Scan count 1, logical reads 15000
Table 'MyChild'. Scan count 0, logical reads 9000 
Table 'MyGrandChild'. Scan count 1, logical reads 7

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

Вполне возможно, можно придумать встречные примеры, где Query 2 работает лучше. Попробуйте оба и посмотрите на планы выполнения.

Мартин Смит
источник
-1

нет такого типа JOIN, который называется «Nested Join». это еще один вариант написания JOIN, предназначенный для удобства чтения. Вы можете видеть их как «Подзапросы» только для понимания цели.

если вы больше беспокоитесь о читаемости кода, то я считаю, что они могут выбирать сами.

И если вас беспокоит производительность запроса, а подсказка «Force JOIN ORDER» не используется в запросе, то не имеет значения, написан ли запрос с помощью «Nested Join» или All «Outer Join». SQL-сервер предлагает порядок, основанный на стоимости соединения двух таблиц и / или результате. SQL Server выполняет СОЕДИНЕНИЕ только между двумя наборами данных.

на самом деле, представьте себе, что во втором случае «вложенное соединение», если SQL-сервер решает выполнить вторую часть, «MyChild AS c LEFT OUTER JOIN MyParent AS p ON p. [id] = c. [ParentId]» и эти таблицы происходят иметь строки, которые собираются отбросить в СЛЕДУЮЩЕМ ЛЕВОМ СОЕДИНЕНИИ. в этом случае сервер SQL потратил ненужные ресурсы на выполнение этих двух операций OUTER JOIN и передачу этого результата следующему JOIN.

Вы также можете посмотреть аналогичный вопрос, заданный и соответствующий ответ здесь. Понимание синтаксиса «вложенного соединения»

Ануп Шах
источник
1
Почему же они создают разные планы запросов без использования FORCE JOIN ORDERподсказки?
Матфея
Без этой подсказки мы не можем гарантировать порядок присоединения, и, как вы видите, другой план выполнения подтверждает это. Например, в первом случае «используя все внешнее соединение» SQL-сервер может выполнить любой из этих двух. сначала «MyChild + MyGrandChild», а затем присоединиться к «MyParent». Или сначала «MyChild + MyParent», а затем присоединиться к «MyGrandChild».
Ануп Шах