結合を表す構文は入れ子結合を許可します。次は項8.2.8.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
しかし、2
つの表現は等価ではありません。たとえば、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
は、「t1
および
t2
の行のカラムと、t3
の各カラムの 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
ではこのテクニックはまだ使われていません。)