8.2.1.12 外部結合の単純化

クエリーの FROM 句内のテーブル式は、多くの場合単純化されます。

パーサー段階で、右外部結合操作を含むクエリーは、左結合操作のみを含む同等のクエリーに変換されます。一般的な場合、変換は次のルールに従って実行されます。

(T1, ...) RIGHT JOIN (T2,...) ON P(T1,...,T2,...) =
(T2, ...) LEFT JOIN (T1,...) ON P(T1,...,T2,...)

形式 T1 INNER JOIN T2 ON P(T1,T2) のすべての内部結合式は、WHERE 条件に (または埋め込まれる結合の結合条件が存在する場合は、それに) 等位項として結合されるリスト T1,T2P(T1,T2) によって、置き換えられます。

オプティマイザは、外部結合操作を含む結合クエリーのプランを評価する際、そのような各操作で、外部テーブルが内部テーブルより前にアクセスされるプランのみを考慮に入れます。そのようなプランのみ、Nested Loop スキーマによって、外部結合操作を含むクエリーを実行できるため、オプティマイザのオプションが制限されます。

次の形式のクエリーがあるとします。

SELECT * T1 LEFT JOIN T2 ON P1(T1,T2)
  WHERE P(T1,T2) AND R(T2)

テーブル T2 の一致する行数を大幅に狭める R(T2) を使用しています。クエリーをそのまま実行した場合、オプティマイザは、テーブル T2 の前にテーブル T1 にアクセスする以外に選択肢がなく、きわめて非効率的な実行プランにつながる可能性があります。

さいわい、MySQL では、WHERE 条件が NULL を受け付けない場合に、それらのクエリーを外部結合操作を含まないクエリーに変換します。条件は、操作のために構築された NULL で補完された行に対し、FALSE または UNKNOWN に評価する場合に、外部結合操作に対して NULL を受け付けないと呼ばれます。

したがって、この外部結合の場合:

T1 LEFT JOIN T2 ON T1.A=T2.A

次のような条件は NULL を受け付けません。

T2.B IS NOT NULL,
T2.B > 3,
T2.C <= T1.C,
T2.B < 2 OR T2.C > 1

次のような条件は NULL を受け付けます。

T2.B IS NULL,
T1.B < 3 OR T2.B IS NOT NULL,
T1.B < 3 OR T2.B > 3

条件が外部結合操作に対して NULL を受け付けるかどうかをチェックする一般的なルールは単純です。条件は次の場合に NULL を受け付けます。

  • その形式が A IS NOT NULL で、A がいずれかの内部テーブルの属性である場合

  • いずれかの引数が NULL である場合に、UNKNOWN に評価する内部テーブルへの参照を含む述語である場合

  • NULL を受け付けない条件を等位項として含む論理積である場合

  • NULL を受け付けない条件の論理和である場合。

条件は、クエリー内で、ある外部結合操作に対しては NULL を受け付けないが、ほかの外部結合操作に対しては NULL を受け付ける場合があります。次のクエリーで:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 LEFT JOIN T3 ON T3.B=T1.B
  WHERE T3.C > 0

WHERE 条件は、2 番目の外部結合操作に対しては NULL を受け付けませんが、最初の外部結合操作に対しては NULL を受け付けます。

WHERE 条件がクエリーの外部結合操作に対して NULL を受け付けない場合、外部結合操作は内部結合操作に置き換えられます。

たとえば、前のクエリーは次のクエリーに置き換えられます。

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 INNER JOIN T3 ON T3.B=T1.B
  WHERE T3.C > 0

元のクエリーでは、オプティマイザは、1 つのアクセス順序 T1,T2,T3 のみと互換性のあるプランを評価します。置換先のクエリーでは、さらにアクセスシーケンス T3,T1,T2 も考慮します。

ある外部結合操作の変換によって、別の操作の変換がトリガーされることがあります。そのため、次のクエリー:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 LEFT JOIN T3 ON T3.B=T2.B
  WHERE T3.C > 0

は、まず次のクエリーに変換されます。

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 INNER JOIN T3 ON T3.B=T2.B
  WHERE T3.C > 0

これは次のクエリーと同等です。

SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3
  WHERE T3.C > 0 AND T3.B=T2.B

ここで、条件 T3.B=T2.B は NULL を受け付けず、外部結合をまったく含まないクエリーを取得するため、残りの外部結合操作を内部結合に置き換えることができます。

SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3
  WHERE T3.C > 0 AND T3.B=T2.B

場合によっては、埋め込まれた外部結合操作を置き換えることに成功しても、埋め込む外部結合を変換できない場合があります。次のクエリー:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A
  WHERE T3.C > 0

は次に変換されます。

SELECT * FROM T1 LEFT JOIN
              (T2 INNER JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A
  WHERE T3.C > 0,

それは埋め込む外部結合操作を含む形式にのみ書き換えることができます。

SELECT * FROM T1 LEFT JOIN
              (T2,T3)
              ON (T2.A=T1.A AND T3.B=T2.B)
  WHERE T3.C > 0.

クエリーに埋め込まれた外部結合操作を変換しようとする場合、WHERE 条件と一緒に埋め込む外部結合に対して、結合条件を考慮する必要があります。次のクエリーで:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A AND T3.C=T1.C
  WHERE T3.D > 0 OR T1.D > 0

WHERE 条件は埋め込まれた外部結合に対しては NULL を受け付けますが、埋め込む外部結合 T2.A=T1.A AND T3.C=T1.C の結合条件は NULL を受け付けません。そのため、クエリーは次に変換できます。

SELECT * FROM T1 LEFT JOIN
              (T2, T3)
              ON T2.A=T1.A AND T3.C=T1.C AND T3.B=T2.B
  WHERE T3.D > 0 OR T1.D > 0

User Comments
Sign Up Login You must be logged in to post a comment.