Co tak naprawdę oznacza pozycja klauzuli ON?

23

Normalna JOIN ... ON ...składnia jest dobrze znana. Ale możliwe jest również ustawienie ONklauzuli oddzielnie od tej JOIN, której odpowiada. Jest to coś, co jest rzadko spotykane w praktyce, nie znajduje się w tutorialach i nie znalazłem żadnego zasobu internetowego, który wspomniałby nawet, że jest to możliwe.

Oto skrypt do zabawy:

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 wygląda normalnie. q2 i q3 mają te nietypowe położenia ONklauzuli.

Ten skrypt niekoniecznie ma sens. Trudno mi było wymyślić sensowny scenariusz.

Co więc oznaczają te niezwykłe wzorce składniowe? Jak to zdefiniowano? Zauważyłem, że nie wszystkie pozycje i zamówienia dla dwóch ONklauzul są dozwolone. Jakie zasady to regulują?

Czy warto pisać takie zapytania?

boot4life
źródło

Odpowiedzi:

32

Jeśli spojrzysz na FROMdiagram składni klauzuli , zobaczysz, że jest tylko jedno miejsce dla ONklauzuli:

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

Myląca jest prosta rekurencja, ponieważ <table_source>w <joined_table> powyżej może być inna <joined_table>:

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

Aby uniknąć nieporozumień, należy użyć nawiasów w nieoczywistych przypadkach (takich jak przykłady), aby wizualnie się rozdzielić <table_sources>; nie są one konieczne do analizatora składni zapytań, ale są przydatne dla ludzi.

mustaccio
źródło
33

Określa logiczne tabele związane z łączeniem.

Z prostym przykładem

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 

#widgets1pozostanie przyłączony zewnętrznie do #widgets2- w wyniku tego powstaje wirtualna tabela, do której przyłączone są wewnętrzne #widgetProperties. Predykatw2.WidgetID = wp.WidgetID będzie oznaczał, że wszystkie puste wiersze zerowe z początkowego sprzężenia zewnętrznego zostaną odfiltrowane, skutecznie tworząc wszystkie połączenia wewnętrzne.

Różni się to od 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

#widgets2jest wewnętrznie połączony z #widgetProperties. Wirtualny stół wynikający z tego złączenia jest następnie prawostronnym stołem w lewym złączeniu zewnętrznym na#widgets1

Ten sam wynik można uzyskać za pomocą tabeli pochodnej lub wspólnego wyrażenia tabelowego ...

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 

... Lub alternatywnie możesz zmienić kolejność wirtualnych tabel i użyć RIGHT JOINzamiast nich.

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 

Jest to omówione tutaj przez Itzika Bena Gana

... warunki JOIN muszą być zgodne z chiastycznym stosunkiem do kolejności tabel. Oznacza to, że jeśli określisz tabele T1, T2, T3 i T4 w tej kolejności, a warunki JOIN będą zgodne z T1 z T2, T2 z T3 i T3 z T4, musisz określić warunki JOIN w kolejności odwrotnej do kolejności tabel , lubię to:

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 

Aby spojrzeć na tę technikę łączenia w inny sposób, dany warunek JOIN może odnosić się tylko do nazw tabel bezpośrednio nad nim lub nazw tabel, o których wcześniej warunki JOIN już się odwoływały i które rozwiązały.

ale artykuł ma wiele nieścisłości, zobacz także list uzupełniający Lubora Kollara .

Martin Smith
źródło
Dzięki Martin, ta odpowiedź jest bardzo pomocna. Ale zaakceptuję ten drugi, ponieważ jego zdanie na temat gramatyki formalnej pomogło mi w pełni zrozumieć problem. W szczególności „związek chiastyczny” wydaje się fałszywym pomysłem. To drzewo, a nie lista plus lista odwrócona. mustaccio zapewnił podstawy do zrozumienia, dlaczego interpretacja Itziksa nie jest całkiem poprawna.
boot4life