Что на самом деле означает позиция предложения ON?

23

Нормальный JOIN ... ON ...синтаксис хорошо известен. Но также возможно расположить ONпредложение отдельно от того, JOINкоторому оно соответствует. Это то, что редко встречается на практике, не встречается в руководствах, и я не нашел ни одного веб-ресурса, который бы даже упоминал, что это возможно.

Вот скрипт, с которым можно поиграть:

SELECT *
INTO #widgets1
FROM (VALUES (1), (2), (3)) x(WidgetID)


SELECT *
INTO #widgets2
FROM (VALUES (1, 'SomeValue1'), (2, 'SomeValue2'), (3, 'SomeValue3')) x(WidgetID, SomeValue)

SELECT *
INTO #widgetProperties
FROM (VALUES
    (1, 'a'), (1, 'b'),
    (2, 'a'), (2, 'b'))
x(WidgetID, PropertyName)


--q1
SELECT w1.WidgetID, w2.SomeValue, wp.PropertyName
FROM #widgets1 w1
LEFT JOIN #widgets2 w2 ON w2.WidgetID = w1.WidgetID
LEFT JOIN #widgetProperties wp ON w2.WidgetID = wp.WidgetID AND wp.PropertyName = 'b'
ORDER BY w1.WidgetID


--q2
SELECT w1.WidgetID, w2.SomeValue, wp.PropertyName
FROM #widgets1 w1
LEFT JOIN #widgets2 w2 --no ON clause here
JOIN #widgetProperties wp
 ON w2.WidgetID = wp.WidgetID AND wp.PropertyName = 'b'
 ON w2.WidgetID = w1.WidgetID
ORDER BY w1.WidgetID


--q3
SELECT w1.WidgetID, w2.SomeValue, wp.PropertyName
FROM #widgets1 w1
LEFT JOIN (
    #widgets2 w2 --no SELECT or FROM here
    JOIN #widgetProperties wp
    ON w2.WidgetID = wp.WidgetID AND wp.PropertyName = 'b')
ON w2.WidgetID = w1.WidgetID
ORDER BY w1.WidgetID

q1 выглядит нормально. q2 и q3 имеют эти необычные положения ONпункта.

Этот сценарий не обязательно имеет большой смысл. Мне было трудно придумать значимый сценарий.

Так что же означают эти необычные синтаксические паттерны? Как это определяется? Я заметил, что не все позиции и заказы по двум ONпунктам разрешены. Каковы правила, регулирующие это?

Кроме того, это когда-нибудь хорошая идея, чтобы писать такие запросы?

boot4life
источник

Ответы:

32

Если вы посмотрите на FROMсинтаксическую диаграмму предложения, то увидите, что есть только одно место для ONпредложения:

<joined_table> ::= 
{
    <table_source> <join_type> <table_source> ON <search_condition> 
    ...
}

Что вас смущает, так это простая рекурсия, потому что <table_source>в <joined_table> выше может быть другое <joined_table>:

[ FROM { <table_source> } [ ,...n ] ] 
<table_source> ::= 
{
    table_or_view_name ... 
    ...
    | <joined_table> 
    ...
}

Чтобы избежать путаницы, вы должны использовать круглые скобки в неочевидных случаях (например, ваших примерах) для визуального разделения <table_sources>; они не нужны для парсера запросов, но полезны для людей.

mustaccio
источник
33

Он определяет логические таблицы, участвующие в объединении.

С простым примером

SELECT w1.WidgetID,
       w2.SomeValue,
       wp.PropertyName
FROM   #widgets1 w1
       LEFT JOIN #widgets2 w2
         ON w2.WidgetID = w1.WidgetID
       JOIN #widgetProperties wp
         ON w2.WidgetID = wp.WidgetID
            AND wp.PropertyName = 'b'
ORDER  BY w1.WidgetID 

#widgets1оставлено внешним, к #widgets2которому присоединен - результат этого формирует виртуальную таблицу, к которой внутренне присоединен #widgetProperties. Предикат w2.WidgetID = wp.WidgetIDбудет означать, что любые пустые расширенные строки из начального внешнего соединения отфильтровываются, что делает все объединения внутренними соединениями.

Это отличается от q2 ...

SELECT w1.WidgetID,
       w2.SomeValue,
       wp.PropertyName
FROM   #widgets1 w1
       LEFT JOIN #widgets2 w2 --no ON clause here
                 JOIN #widgetProperties wp
                   ON w2.WidgetID = wp.WidgetID
                      AND wp.PropertyName = 'b'
         ON w2.WidgetID = w1.WidgetID
ORDER  BY w1.WidgetID

#widgets2это внутреннее соединение #widgetProperties. Виртуальная таблица, полученная в результате этого объединения, будет тогда правой таблицей в левом внешнем соединении на#widgets1

Тот же результат может быть достигнут с использованием производной таблицы или выражения общей таблицы ...

WITH VT2
     AS (SELECT w2.WidgetID,
                w2.SomeValue,
                wp.PropertyName
         FROM   #widgets2 w2 
                JOIN #widgetProperties wp
                  ON w2.WidgetID = wp.WidgetID
                     AND wp.PropertyName = 'b')
SELECT w1.WidgetID,
       VT2.SomeValue,
       VT2.PropertyName
FROM   #widgets1 w1
       LEFT JOIN VT2
         ON VT2.WidgetID = w1.WidgetID
ORDER  BY w1.WidgetID 

... Или вы можете изменить порядок виртуальных таблиц и использовать RIGHT JOINвместо них.

SELECT w1.WidgetID,
       w2.SomeValue,
       wp.PropertyName
FROM   #widgets2 w2
       INNER JOIN #widgetProperties wp
               ON w2.WidgetID = wp.WidgetID
                  AND wp.PropertyName = 'b'
       RIGHT JOIN #widgets1 w1
               ON w2.WidgetID = w1.WidgetID
ORDER  BY w1.WidgetID 

Это покрыто Ицик Бен Ган здесь

... условия соединения должны следовать хиастическому отношению к порядку столов. То есть, если вы указываете таблицы T1, T2, T3 и T4 в этом порядке и условия JOIN совпадают с T1 с T2, T2 с T3 и T3 с T4, вы должны указать условия JOIN в порядке, противоположном порядку таблицы , как это:

FROM   T1
       <join_type> T2 T2
                  <join_type> T3 T3
                             <join_type> T4
                               ON T4.key = T3.key
                    ON T3.key = T2.key
         ON T2.key = T1.key 

Чтобы взглянуть на эту технику соединения по-другому, данное условие JOIN может ссылаться только на имена таблиц прямо над ним или имена таблиц, на которые более ранние условия JOIN уже ссылались и разрешались.

но в статье есть ряд неточностей, см. также последующее письмо Любора Коллара .

Мартин Смит
источник
Спасибо Мартин, этот ответ очень полезен. Я приму другой, хотя, потому что его пункт о формальной грамматике помог мне полностью понять проблему. В частности, «хиастические отношения» кажутся ложной идеей. Это дерево, а не список плюс перевернутый список. Мустаччо предоставил структуру, чтобы понять, почему интерпретация Ицикса не совсем верна.
boot4life