¿Qué significa realmente la posición de la cláusula ON?

23

La JOIN ... ON ...sintaxis normal es bien conocida. Pero también es posible colocar la ONcláusula por separado de la JOINque corresponde. Esto es algo que rara vez se ve en la práctica, no se encuentra en los tutoriales y no he encontrado ningún recurso web que incluso mencione que esto es posible.

Aquí hay un script para jugar:

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 se ve normal. q2 y q3 tienen estas posiciones inusuales de la ONcláusula.

Este script no necesariamente tiene mucho sentido. Fue difícil para mí idear un escenario significativo.

Entonces, ¿qué significan estos patrones de sintaxis inusuales? ¿Cómo se define esto? Noté que no todas las posiciones y órdenes para las dos ONcláusulas están permitidas. ¿Cuáles son las reglas que gobiernan esto?

¿También es una buena idea escribir consultas como esta?

boot4life
fuente

Respuestas:

32

Si observa el FROMdiagrama de sintaxis de la cláusula , verá que solo hay un lugar para la ONcláusula:

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

Lo que encuentras confuso es una simple recursión, porque <table_source>en <joined_table> arriba puede ser otro <joined_table>:

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

Para evitar confusiones, debe usar paréntesis en casos no obvios (como sus ejemplos) para separar visualmente <table_sources>; no son necesarios para el analizador de consultas pero son útiles para humanos.

mustaccio
fuente
33

Determina las tablas lógicas involucradas en la unión.

Con un simple ejemplo

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 

#widgets1se deja unida externamente a #widgets2- el resultado de eso forma una tabla virtual que se une internamente a #widgetProperties. El predicado w2.WidgetID = wp.WidgetIDsignificará que cualquier fila extendida nula de la unión externa inicial se filtra, haciendo que todas las uniones sean internas.

Esto difiere de 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

#widgets2es interior unido en #widgetProperties. La tabla virtual resultante de esa combinación es la tabla de la derecha en la combinación externa izquierda en#widgets1

Se puede lograr el mismo resultado utilizando una tabla derivada o una expresión de tabla común ...

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 

... O, como alternativa, puede volver a ordenar las tablas virtuales y utilizar un RIGHT JOINen su lugar.

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 

Esto está cubierto por Itzik Ben Gan aquí

... las condiciones de UNIÓN deben seguir una relación quiástica con el orden de la tabla. Es decir, si especifica las tablas T1, T2, T3 y T4 en ese orden y las condiciones JOIN coinciden con T1 con T2, T2 con T3 y T3 con T4, debe especificar las condiciones JOIN en el orden opuesto al orden de la tabla , Me gusta esto:

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 ver esta técnica de unión de una manera diferente, una condición JOIN dada puede referirse solo a los nombres de tabla justo encima de ella o los nombres de tabla a los que las condiciones JOIN anteriores ya se han referido y resuelto.

pero el artículo tiene una serie de inexactitudes, consulte también la carta de seguimiento de Lubor Kollar .

Martin Smith
fuente
Gracias Martin, esta respuesta es muy útil. Sin embargo, aceptaré el otro, porque su punto sobre la gramática formal fue lo que me ayudó a comprender completamente el problema. En particular, la "relación quiástica" parece ser una idea falsa. Es un árbol, no una lista más una lista invertida. Mustaccio proporcionó el marco para comprender por qué la interpretación de Itziks no es del todo correcta.
boot4life