結合を表す構文は入れ子結合を許可します。以下は項12.2.7.1. 「JOIN
構文」で記述された結合構文に関連します。
table_factor
構文はSQL標準と比較して拡張されています。後者はtable_reference
のみ受付、かっこ内のリストは受け付けません。これは、table_reference
アイテムのリスト内の点(、)が内部結合と等価とする場合、この拡張は控えめです。例
:
SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
は以下と等価です。
SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
MySQLでは、CROSS JOIN
はINNER
JOIN
と構文上等価です
(置き換え可能です)。標準SQL上等価ではありません。INNER
JOIN
はON
節と一緒に使用されます。CROSS
JOIN
は他の使用方法があります。
一般的に、inner joinオペレーションを含むjoin 表現のかっこは無視できます。かっこを取り除きグルーピングオペレーションを左に移動させた後、join 表現は:
t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL) ON t1.a=t2.a
以下の表現に変換されます。
(t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL
しかし、二つの表現は等価ではありません。たとえば、t1
、t2
、そしてt3
が以下の状態であるとします。
テーブルt1
は(1)
、(2)
を含む
テーブルt2
は(1,101)
行を含む
テーブルt3
は(101)
行を含む
この場合、最初の表現は(1,1,101,101)
、そして(2,NULL,NULL,NULL)
を含む行の結果セットを返します。2番目の表現は(1,1,101,101)
、(2,NULL,NULL,101)
を含む行を返します。
mysql>SELECT *
->FROM t1
->LEFT JOIN
->(t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
->ON t1.a=t2.a;
+------+------+------+------+ | a | a | b | b | +------+------+------+------+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | NULL | +------+------+------+------+ mysql>SELECT *
->FROM (t1 LEFT JOIN t2 ON t1.a=t2.a)
->LEFT JOIN t3
->ON t2.b=t3.b OR t2.b IS NULL;
+------+------+------+------+ | a | a | b | b | +------+------+------+------+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | 101 | +------+------+------+------+
下記の例では、外側 join オペレーションが内側 join オペレーションと一緒に使用されます。
t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
その表現は以下の表現に変換できません。
t1 LEFT JOIN t2 ON t1.a=t2.a, t3.
既存のテーブル状態では、以下の2表現は異なる行セットを返します。
mysql>SELECT *
->FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;
+------+------+------+------+ | a | a | b | b | +------+------+------+------+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | NULL | +------+------+------+------+ mysql>SELECT *
->FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;
+------+------+------+------+ | a | a | b | b | +------+------+------+------+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | 101 | +------+------+------+------+
よって、外側 join 演算子を含む join 表現のかっこを取り除いた場合、元の表現の結果セットを変える可能性があります。
正確には、左外側 join オペレーションの右演算子のかっこを、そして右側 join オペレーションの左演算子のかっこを無視することができません。言い換えれば、外側 join オペレーションの内側テーブル表現のかっこを無視することはできません。他のオペランド(外側テーブルのオペランド)のかっこは無視できます。
以下の表現:
(t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b)
はこの表現と等価です:
t1, t2 LEFT JOIN t3 ON P(t2.b,t3.b)
テーブルt1,t2,t3
と条件P
属性t2.b
and t3.b
.
join 表現(join_table
)
のjoin オペレーション実行順序が左から右でない場合、入れ子
join の話が出てきます。以下のクエリを考慮してください。
SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a WHERE t1.a > 1 SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a WHERE (t2.b=t3.b OR t2.b IS NULL) AND t1.a > 1
上記クエリは以下の入れ子 join が含まれると考えられています。
t2 LEFT JOIN t3 ON t2.b=t3.b t2, t3
最初のクエリでは、左 join オペレーショを使用して入れ子 join が生成されます。二番目のクエリでは内側 join オペレーションで生成されます。
最初のクエリでは、かっこは取り除いてもかまいません。join
表現の文法構成はjoin
オペレーションと同じ実行順序を指令します。2番目のクエリでは、かっこなしでも join
表現があいまいに解釈されますが、かっこは取り除くことができません。(拡張された構文では、理論上はかっこなしでもパースされますが、2番目クエリの(t2,
t3)
のかっこは必要です。まだ、クエリはあいまいでない構文構成になります。これはLEFT
JOIN
とON
が左と右のデリミタの役割りを右の表現(t2,t3)
で果たすからです。)
前述の例でこれらの点を証明しています。
インナーjoins のみ関する表現(アウターjoins は不可)については、かっこは取り除けます。かっこを取り除いて左から右に評価を行うことができます(あるいは、テーブルの評価は好きな順序で行えます)。
一般的に、そとがわjoinや外がわjoin と併合された内側join にとっては、同じではありません。かっこを取り除くことで結果を変えることがあるかもしれません。
入れ子外側joins を含むクエリは内側join
を含むクエリと同じように、パイプライン形式で実行されます。正確には、入れ子ループjoinアルゴリズムが利用されます。入れ子ループjoinがクエリを実行する際利用するアルゴリズムスキーマを思い出してください。例えば、3つのテーブルT1,T2,T3
に関するjoinクエリが、以下のフォームであるとします。
SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2) INNER JOIN T3 ON P2(T2,T3) WHERE P(T1,T2,T3).
ここでは、
P1(T1,T2)
とP2(T3,T3)
はjoin 条件です(表現につく)。それに引き換え、P(t1,t2,t3)
はテーブルT1,T2,T3
カラム上の条件です。
入れ子ループjoinアルゴリズムはこのクエリを次のように実行します。
FOR each row t1 in T1 { FOR each row t2 in T2 such that P1(t1,t2) { FOR each row t3 in T3 such that P2(t2,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } } }
t1||t2||t3
を使用した表記法は「t1
、t2
、そしてt3
行を含むカラムを連鎖させることで作成された行」を意味します。以下の例では、行の名前があらわれる箇所にNULL
とある場合、NULL
はその行の各カラムに使用されることを意味します。例えば、t1||t2||NULL
は「t3
の各カラム毎のt1
、t2
、そしてNULL
行のカラムを連鎖させることで作成された行。」
入れ子のある外側join クエリを見てみましょう。
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON P2(T2,T3)) ON P1(T1,T2) WHERE P(T1,T2,T3).
このクエリでは、入れ子ループパターンを改良することで以下を取得します。
FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) { BOOL f2:=FALSE; FOR each row t3 in T3 such that P2(t2,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f2=TRUE; f1=TRUE; } IF (!f2) { IF P(t1,t2,NULL) { t:=t1||t2||NULL; OUTPUT t; } f1=TRUE; } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } } }
一般的に、外側
joinオペレーションの最初の内側テーブル入れ子ループにとって、ループ前に消され、ループ後にチェックされるフラグが導入されます。フラグがオンになるのは、内側オペランドを表すテーブルから外側テーブルの現在行にマッチが見つかったときです。ループサイクルの最後でフラグがOFFの場合は、外側テーブルの現在行でマッチが見つからなかったときです。この場合、行がインナーテーブルのカラムNULL
値で補われています。結果行は次の入れ子ループか出力へ、最終確認のため渡されますが、これは行が組み込まれた全ての外側
joinの条件を満たしている場合のみです。
この例では、次の表現で表された外側joinテーブルは組み込まれています。
(T2 LEFT JOIN T3 ON P2(T2,T3))
内側joinを含むクエリにとって、オプティマイザは以下のような異なる順序の入れ子ループが選択できることに注目してください。
FOR each row t3 in T3 { FOR each row t2 in T2 such that P2(t2,t3) { FOR each row t1 in T1 such that P1(t1,t2) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } } }
外側テーブルを含むクエリに関しては、オプティマイザは外側テーブルのループが内側テーブルのループの前にくる順序のみ選択可能です。よって、外側joinのクエリにとって、1つの入れ子順序のみ可能となります。以下のクエリでは、2つの異なる入れ子を評価します。
SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3) WHERE P(T1,T2,T3)
以下が入れ子です。
FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) { FOR each row t3 in T3 such that P2(t1,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f1:=TRUE } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } } }
そして
FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t3 in T3 such that P2(t1,t3) { FOR each row t2 in T2 such that P1(t1,t2) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f1:=TRUE } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } } }
両の入れ子にとって、T1
は外側joinで使用されているため、外側ループでプロセスされなければいけません。
T2
とT3
は内側joinで使用されているため、そのjoinは内側ループで処理されなければいけません。ただし、joinが内側joinのため、T2
とT3
はどちらの順序でも処理できます。
内側joinの入れ子ループアルゴリズムについては、クエリ実行性能に関する、重大な詳細を省きました。いわゆる、「後入れ先出し」条件に関することには触れませんでした。例えば、WHERE
条件P(T1,T2,T3)
が接続法によって表されるとします。
P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3).
この場合、MySQLは内側joinを含むクエリの実行には以下の入れ子ループスキーマを使用します。
FOR each row t1 in T1 such that C1(t1) { FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2) { FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } } }
ここで、書く接続詞C1(T1)
、C2(T2)
、C3(T3)
は評価が可能なよう、最も内側にあるループから最も外側にあるループまで押し出されます。もしC1(T1)
が制限力の高い条件である場合、この条件の後入れ先出しはテーブルT1
から内側ループに渡される行の数を大幅に減らします。結果的に、クエリの実行時間が大きく短縮できます。
外側joinを含むクエリについては、外側テーブルの現在行に内側テーブルからのマッチがあることが確認できてからWHERE
条件がチェックされます。よって、内側入れ子ループの後だし先入れ条件最適化は外側joinを含むクエリには直接適用できません。ここではマッチが見つかった時に起動するフラグに守られた、条件つき後出し先入れの述語を紹介しなければいけません。
例えば、外側joinでは
P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)
ガードされた後だし先入れ条件を使用した入れ子ループスキーマは以下のようになります。
FOR each row t1 in T1 such that C1(t1) { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) AND (f1?C2(t2):TRUE) { BOOL f2:=FALSE; FOR each row t3 in T3 such that P2(t2,t3) AND (f1&&f2?C3(t3):TRUE) { IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) { t:=t1||t2||t3; OUTPUT t; } f2=TRUE; f1=TRUE; } IF (!f2) { IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) { t:=t1||t2||NULL; OUTPUT t; } f1=TRUE; } } IF (!f1 && P(t1,NULL,NULL)) { t:=t1||NULL||NULL; OUTPUT t; } }
一般的に、後だし先入れ述語はP1(T1,T2)
やP(T2,T3)
といったjoin条件から抽出できます。この場合、後だし先入れ述語は対応する外側joinオペレーションNULL
-に補われた行のチェックを妨げるフラグによって守られています。
ここで、1つの内側テーブルから同じ入れ子joinへのアクセスキーは、WHERE
条件からの述語に誘導されている場合、禁止されています。(この場合条件つきのキーアクセスを使用することはできますが、MySQLではこのテクニックはまだ使われていません5.1。)