Cosa significa effettivamente la posizione della clausola ON?


23

La JOIN ... ON ...sintassi normale è ben nota. Ma è anche possibile posizionare la ONclausola separatamente da JOINquella a cui corrisponde. Questo è qualcosa che raramente si vede nella pratica, non si trova nei tutorial e non ho trovato alcuna risorsa web che menzioni nemmeno che ciò sia possibile.

Ecco una sceneggiatura con cui giocare:

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 sembra normale. q2 e q3 hanno questi posizionamenti insoliti della ONclausola.

Questo script non ha necessariamente molto senso. È stato difficile per me elaborare uno scenario significativo.

Cosa significano questi insoliti schemi di sintassi? Come viene definito? Ho notato che non tutte le posizioni e gli ordini per le due ONclausole sono consentiti. Quali sono le regole che governano questo?

Inoltre è mai una buona idea scrivere domande come questa?

Risposte:


32

Se guardi il FROMdiagramma della sintassi della clausola vedrai che c'è solo un posto per la ONclausola:

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

Ciò che trovi confuso è una semplice ricorsione, perché <table_source>in <joined_table> sopra può esserci un altro <joined_table>:

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

Per evitare confusione dovresti usare le parentesi in casi non ovvi (come i tuoi esempi) per separare visivamente <table_sources>; non sono necessari per il parser di query ma utili per l'uomo.


33

Determina le tabelle logiche coinvolte nel join.

Con un semplice esempio

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 

#widgets1viene lasciato unito a #widgets2- il risultato di ciò forma una tabella virtuale a cui è unito #widgetProperties. Il predicato w2.WidgetID = wp.WidgetIDsignificherà che tutte le righe estese null dal join esterno iniziale vengono filtrate, rendendo effettivamente tutti i join interni.

Questo differisce da 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è interiore unito su #widgetProperties. La tabella virtuale risultante da quel join è quindi la tabella di destra nel Join esterno sinistro attivo#widgets1

Lo stesso risultato può essere ottenuto utilizzando una tabella derivata o Common Table Expression ...

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 in alternativa puoi riordinare le tabelle virtuali e utilizzare RIGHT JOINinvece un .

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 

Questo è coperto da Itzik Ben Gan qui

... le condizioni JOIN devono seguire una relazione chiastica con l'ordine delle tabelle. Cioè, se si specificano le tabelle T1, T2, T3 e T4 in quell'ordine e le condizioni JOIN corrispondono a T1 con T2, T2 con T3 e T3 con T4, è necessario specificare le condizioni JOIN nell'ordine opposto all'ordine della tabella , come questo:

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 

Per esaminare questa tecnica di join in un modo diverso, una determinata condizione JOIN può riferirsi solo ai nomi delle tabelle proprio sopra di essa o ai nomi delle tabelle a cui le condizioni JOIN precedenti erano già citate e risolte.

ma l'articolo presenta una serie di inesattezze, vedi anche la lettera di follow-up di Lubor Kollar .


Grazie Martin, questa risposta è molto utile. Accetterò l'altro, perché il suo punto sulla grammatica formale è stato ciò che mi ha aiutato a comprendere appieno il problema. In particolare la "relazione chiastica" sembra essere una falsa idea. È un albero, non un elenco più un elenco invertito. Mustaccio ha fornito il quadro per capire perché l'interpretazione di Itziks non è del tutto corretta.
boot4life
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.