O que realmente significa a posição da cláusula ON?

23

A JOIN ... ON ...sintaxe normal é bem conhecida. Mas também é possível posicionar a ONcláusula separadamente da JOINque ela corresponde. Isso é algo que raramente é visto na prática, não encontrado nos tutoriais e não encontrei nenhum recurso da Web que até mencione que isso é possível.

Aqui está um script para brincar:

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 parece normal. q2 e q3 têm esses posicionamentos incomuns da ONcláusula.

Esse script não faz necessariamente muito sentido. Foi difícil para mim inventar um cenário significativo.

Então, o que significam esses padrões incomuns de sintaxe? Como isso é definido? Percebi que nem todas as posições e pedidos das duas ONcláusulas são permitidos. Quais são as regras que regem isso?

Também é uma boa ideia escrever consultas como essa?

boot4life
fonte

Respostas:

32

Se você olhar o FROMdiagrama de sintaxe da cláusula , verá que há apenas um lugar para a ONcláusula:

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

O que você acha confuso é simples recursão, porque <table_source>em <joined_table> acima pode ser outro <joined_table>:

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

Para evitar confusão, você deve usar parênteses em casos não óbvios (como seus exemplos) para separar visualmente <table_sources>; eles não são necessários para o analisador de consultas, mas são úteis para humanos.

mustaccio
fonte
33

Ele determina as tabelas lógicas envolvidas na associação.

Com um exemplo simples

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é deixado externo associado a #widgets2- o resultado disso forma uma tabela virtual que é interna associada #widgetProperties. O predicado w2.WidgetID = wp.WidgetIDsignifica que qualquer linha estendida nula da junção externa inicial é filtrada, efetivamente tornando todas as junções internas.

Isso difere do 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é interior unido #widgetProperties. A tabela virtual resultante dessa junção é a tabela à direita na junção externa esquerda em#widgets1

O mesmo resultado pode ser alcançado usando uma tabela derivada ou Expressão de Tabela Comum ...

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 

... Ou, como alternativa, você pode reordenar as tabelas virtuais e usar um 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 

Isto é coberto por Itzik Ben Gan aqui

... as condições JOIN devem seguir um relacionamento quiastico com a ordem da tabela. Ou seja, se você especificar as tabelas T1, T2, T3 e T4 nessa ordem e as condições JOIN corresponderem a T1 com T2, T2 com T3 e T3 com T4, deverá especificar as condições JOIN na ordem oposta à ordem da tabela , como isso:

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 

Para examinar essa técnica de junção de uma maneira diferente, uma determinada condição JOIN pode se referir apenas aos nomes das tabelas logo acima ou aos nomes das tabelas às quais as condições JOIN anteriores já se referiram e resolveram.

mas o artigo tem várias imprecisões, veja também a carta de acompanhamento de Lubor Kollar .

Martin Smith
fonte
Obrigado Martin, esta resposta é muito útil. Eu aceitarei a outra, porque seu argumento sobre a gramática formal foi o que me ajudou a entender completamente o problema. Em particular, o "relacionamento quiático" parece ser uma idéia falsa. É uma árvore, não uma lista mais uma lista invertida. mustaccio forneceu a estrutura para entender por que a interpretação de Itziks não está certa.
Boot4life