はじめに
パフォーマンスと一貫性の目的でデータを個別のテーブルに分離することが役立つことが多いですが、特定のリクエストに答えるためには、多くの場合、複数のテーブルからのデータを参照する必要があります。テーブル結合は、共通のフィールド値に基づいて各レコードを照合することにより、さまざまなテーブルからのデータを結合する方法です。
いくつかの異なる種類の結合があり、テーブルレコードを結合するさまざまな方法を提供します。この記事では、MySQLが結合をどのように実装しているか、およびそれぞれが最も役立つシナリオについて説明します。
結合とは?
簡単に言えば、結合は、複数のテーブルからデータを表示する方法です。特定の列の一致する値に基づいて、異なるソースからのレコードを結合することによりこれを行います。結果の各行は、最初のテーブルのレコードと、各テーブルの1つ以上の列に同じ値を持つことに基づいて、2番目のテーブルの行を組み合わせたもので構成されます。
結合の基本的な構文は次のとおりです
SELECT*FROM<first_table><join_type> <second_table><join_condition>;
結合では、結果の各行は、最初のテーブルのすべての列の後に、2番目のテーブルのすべての列を含めることによって構築されます。クエリのSELECT
部分を使用して、表示する正確な列を指定できます。
比較に使用される列の値が一意でない場合、元のテーブルから複数の行が構築される場合があります。たとえば、最初のテーブルから比較されている列に「red」の値を持つ2つのレコードがあると想像してください。これに一致するのは、その値を持つ3行を持つ2番目のテーブルからの列です。結合は、達成可能なさまざまな組み合わせを表すその値に対して6つの異なる行を生成します。
結合の種類と結合条件によって、表示される各行がどのように構築されるかが決まります。これは、結合条件に一致するものと一致しないものの両方を持つ各テーブルの行に何が起こるかに影響を与えます。
便宜上、多くの結合は、一方のテーブルの主キーを2番目のテーブルの関連する外部キーと一致させます。主キーと外部キーは、データベースシステムが一貫性保証を維持するためにのみ使用されますが、それらの関係は多くの場合、結合条件の適切な候補となります。
結合のさまざまな種類
さまざまな種類の結合が利用可能であり、それぞれが異なる結果を生成する可能性があります。各タイプがどのように構築されているかを理解することは、さまざまなシナリオに適切なタイプを決定するのに役立ちます。
内部結合とクロス結合
デフォルトの結合は内部結合と呼ばれます。MySQLでは、これはINNER JOIN
、JOIN
、またはCROSS JOIN
のいずれかを使用して指定できます。他のデータベースシステムでは、INNER JOIN
とCROSS JOIN
は多くの場合、2つの別個の概念ですが、MySQLは同じ構成でそれらを実装します。
内部結合の構文を示す典型的な例を次に示します
SELECT*FROMtable_1[INNER] JOIN table_2ON table_1.id = table_2.table_1_id;
内部結合は最も制限の厳しい結合タイプです。これは、各テーブルの行を結合して作成された行のみを表示するためです。構成テーブルで行が一致する相手を持たない行は、結果から削除されます。たとえば、最初のテーブルの比較列に「blue」の値があり、2番目のテーブルにその値を持つレコードがない場合、その行は出力から抑制されます。
結果を構成テーブルのベン図として表す場合、内部結合を使用すると、2つの円の重なり合う領域を表すことができます。いずれかのテーブルにのみ存在する値は表示されません。
上記のように、MySQLはこの形式を使用してクロス結合も生成します。MySQLでは、一致条件なしで内部結合を使用してクロス結合を生成できます。クロス結合は、各テーブルの行が互いに一致するかどうかを判断するために比較を使用しません。代わりに、結果は、最初のテーブルの各行を2番目のテーブルの各行に単純に追加することによって構築されます。
これにより、2つ以上のテーブルの行のデカルト積が生成されます。事実上、このスタイルの結合は、各テーブルからの行を無条件に結合します。したがって、各テーブルに3行がある場合、結果のテーブルは両方のテーブルのすべての列を含む9行になります。
たとえば、t1
というテーブルとt2
というテーブルがあり、それぞれにr1
、r2
、およびr3
の行がある場合、結果は次のように結合された9行になります
t1.r1 + t2.r1t1.r1 + t2.r2t1.r1 + t2.r3t1.r2 + t2.r1t1.r2 + t2.r2t1.r2 + t2.r3t1.r3 + t2.r1t1.r3 + t2.r2t1.r3 + t2.r3
左結合
左結合は、内部結合で見つかったすべてのレコードと、最初のテーブルからの一致しないすべての行を表示する結合です。MySQLでは、これはLEFT OUTER JOIN
またはLEFT JOIN
として指定できます。
左結合の基本的な構文は、次のパターンに従います
SELECT*FROMtable_1LEFT JOIN table_2ON table_1.id = table_2.table_1_id;
左結合は、最初に内部結合を実行して、両方のテーブルの一致するすべてのレコードから行を構築することによって構築されます。その後、最初のテーブルからの一致しないレコードも含まれます。結合の各行には両方のテーブルの列が含まれているため、一致しない列は、2番目のテーブルのすべての列の値としてNULL
を使用します。
結果を構成テーブルのベン図として表す場合、左結合を使用すると、左側の円全体を表すことができます。2つの円の交差によって表される左側の円の部分には、右側のテーブルによって補足された追加データが含まれます。
右結合
右結合は、内部結合で見つかったすべてのレコードと、2番目のテーブルからの一致しないすべての行を表示する結合です。MySQLでは、これはRIGHT OUTER JOIN
またはRIGHT JOIN
として指定できます。
右結合の基本的な構文は、次のパターンに従います
SELECT*FROMtable_1RIGHT JOIN table_2ON table_1.id = table_2.table_1_id;
右結合は、最初に内部結合を実行して、両方のテーブルの一致するすべてのレコードから行を構築することによって構築されます。その後、2番目のテーブルからの一致しないレコードも含まれます。結合の各行には両方のテーブルの列が含まれているため、一致しない列は、最初のテーブルのすべての列の値としてNULL
を使用します。
結果を構成テーブルのベン図として表す場合、右結合を使用すると、右側の円全体を表すことができます。2つの円の交差によって表される右側の円の部分には、左側のテーブルによって補足された追加データが含まれます。
移植性の理由から、MySQLは可能な場合は右結合の代わりに左結合を使用することを推奨しています。
完全外部結合
完全結合は、内部結合で見つかったすべてのレコードと、両方の構成テーブルからの一致しないすべての行を表示する結合です。MySQLは完全結合をネイティブに実装していませんが、いくつかのトリックを使用して動作をエミュレートできます。
完全外部結合の結果を複製するには、左結合を実行して、両方のテーブルで共有されているすべての結果と、左側のテーブルからの一致しないすべての行を取得します。次に、UNION ALL
セット演算子を使用して、これらの結果を右側のテーブルの「アンチ結合」と結合します。「アンチ結合」は、テーブル間で共通でない結果のみを検索する結合操作です。
完全結合の基本的な構文は、次のパターンに従います
( SELECT*FROMtable_1LEFT JOIN table_2ON table_1.id = table_2.table_1_id)UNION ALL( SELECT*FROMtable_1RIGHT JOIN table_2ON table_1.id = table_2.table_1_idWHERE table_1.id IS NULL);
結合の各行には両方のテーブルの列が含まれているため、一致しない列は、一致しない他のテーブルのすべての列の値としてNULL
を使用します。
結果を構成テーブルのベン図として表す場合、完全結合を使用すると、両方の構成円全体を表すことができます。2つの円の交差部分には、各構成テーブルから提供された値が含まれます。重なり合う領域の外側の円の部分には、それらが属するテーブルからの値が含まれ、NULL
を使用して、他のテーブルにある列を埋めます。
自己結合
自己結合は、テーブルの行をそれ自体と結合する結合です。これがどのように役立つかはすぐには明らかにならないかもしれませんが、実際には多くの一般的なアプリケーションがあります。
多くの場合、テーブルは、互いに関係して複数の役割を果たすことができるエンティティを記述します。たとえば、people
のテーブルがある場合、各行には、テーブル内の他のpeople
を参照するmother
列が含まれている可能性があります。自己結合を使用すると、これらの異なる行を、2番目のテーブルのインスタンスを、これらの値が一致する最初のテーブルに結合することにより、まとめて結合できます。
自己結合は同じテーブルを2回参照するため、参照を曖昧さをなくすためにテーブルエイリアスが必要です。たとえば、上記の例では、エイリアスpeople AS children
とpeople AS mothers
を使用して、people
テーブルの2つのインスタンスを結合できます。そうすれば、結合条件を定義するときに、参照しているテーブルのインスタンスを指定できます。
別の例を次に示します。今回は、従業員とマネージャー間の関係を表しています
SELECT*FROMpeople AS employeeJOIN people AS managerON employee.manager_id = manager.id;
結合条件
テーブルを結合する場合、結合条件は、行を組み合わせて複合結果を形成する方法を決定します。基本的な前提は、結合がその行で発生するために一致する必要がある各テーブルの列を定義することです。
ON
句
テーブル結合の条件を定義する最も標準的な方法は、ON
句を使用することです。ON
句は、等号を使用して、結合が発生するタイミングを決定するために比較される各テーブルの正確な列を指定します。MySQLは、提供された列を使用して、各テーブルの行を結合します。
ON
句は最も冗長ですが、利用可能な結合条件の中で最も柔軟性があります。結合される各テーブルの列名がどれほど標準化されているかに関係なく、具体性を可能にします。
ON
句の基本的な構文は次のようになります
SELECT*FROMtable1JOINtable2ONtable1.id = table2.ident;
ここで、table1
のid
列がtable2
のident
列と一致するたびに、table1
とtable2
の行が結合されます。内部結合が使用されているため、結果には結合された行のみが表示されます。クエリはワイルドカード*
文字を使用しているため、両方のテーブルのすべての列が表示されます。
これは、結合条件を満たすことによって、table1
のid
列とtable2
のident
列の両方が、まったく同じ値を持っているにもかかわらず表示されることを意味します。SELECT
列リストに表示する正確な列を呼び出すことで、この重複を回避できます。
USING
句
USING
句は、比較される列が両方のテーブルで同じ名前を持つ場合に使用できるON
句の条件を指定するためのショートカットです。USING
句は、比較する必要のある共有列名のリストを括弧で囲んで取得します。
USING
句の一般的な構文は、この形式を使用します
SELECT*FROMtable1JOINtable2USING(id, state);
この結合は、両方のテーブルが共有する2つの列(id
とstate
)がそれぞれ一致する値を持つ場合に、table1
をtable2
と結合します。
この同じ結合は、ON
を使用してより冗長に表現できます
SELECT*FROMtable1JOINtable2ONtable1.id = table2.id AND table1.state = table2.state;
上記の結合の両方が、同じデータが存在する同じ行を構築することになりますが、表示はわずかに異なります。ON
句には両方のテーブルのすべての列が含まれていますが、USING
句は重複する列を抑制します。したがって、2つの別個のid
列と2つの別個のstate
列(テーブルごとに1つ)がある代わりに、結果には共有列のそれぞれ1つと、table1
とtable2
によって提供される他のすべての列のみが含まれます。
NATURAL
句
NATURAL
句は、USING
句の冗長性をさらに削減できるもう1つのショートカットです。NATURAL
結合は、一致させる列を指定しません。代わりに、MySQLは、各データベースで一致する列を持つすべての列に基づいてテーブルを自動的に結合します。
NATURAL
結合句の一般的な構文は次のようになります
SELECT*FROMtable1NATURAL JOINtable2;
table1
とtable2
の両方にid
、state
、およびcompany
という名前の列があると仮定すると、上記のクエリはON
句を使用した次のクエリと同等になります
SELECT*FROMtable1JOINtable2ONtable1.id = table2.id AND table1.state = table2.state AND table1.company = table2.company;
そして、USING
句を使用したこのクエリ
SELECT*FROMtable1JOINtable2USING(id, state, company);
USING
句と同様に、NATURAL
句は重複する列を抑制するため、結果には結合された各列の単一のインスタンスのみが存在します。
NATURAL
句はクエリの冗長性を減らすことができますが、使用する際は注意が必要です。テーブルの結合に使用される列が自動的に計算されるため、構成テーブルの列が変更されると、新しい結合条件により結果が大きく異なる可能性があります。
結合条件とWHERE
句
結合条件は、WHERE
句を使用してデータの行をフィルタリングするために使用される比較と多くの特性を共有しています。どちらの構造も、行が考慮されるためにtrueに評価する必要がある式を定義します。このため、WHERE
構造に追加の比較を含めることと、結合句自体の中でそれらを定義することの違いが常に直感的であるとは限りません。
結果として生じる違いを理解するためには、MySQLがクエリの異なる部分を処理する順序を見ていく必要があります。この場合、結合条件の述語が最初に処理され、メモリ内に仮想結合テーブルが構築されます。この段階の後、WHERE
句内の式が評価され、結果の行がフィルタリングされます。
例として、customers
とorders
という2つのテーブルがあり、結合する必要があるとします。customers.id
列をorders.customer_id
列と一致させて2つのテーブルを結合します。さらに、orders
テーブルのproduct_id
が12345の行に関心があります。
上記の要件を考えると、注意すべき2つの条件があります。ただし、これらの条件を表現する方法によって、受け取る結果が決まります。
まず、両方をLEFT JOIN
の結合条件として使用しましょう
SELECTcustomers.id AS customers_id,customers.name,orders.id AS orders_id,orders.product_idFROMcustomersLEFT JOINordersONcustomers.id = orders.customers_id AND orders.product_id = 12345;
結果は次のようになる可能性があります
+--------------+----------+-----------+------------+customers_id | name | orders_id | product_id |+--------------+----------+-----------+------------+20 | Early Co | NULL | NULL |320 | Other Co | 680 | 12345 |4380 | Acme Co | 182 | 12345 |4380 | Acme Co | 480 | 12345 |8033 | Big Co | NULL | NULL |+--------------+----------+-----------+------------+5 rows in set (0.00 sec)
MySQLは、次の操作を実行してこの結果に到達しました
- 次の条件に該当する場合に、
customers
テーブルの任意の行をorders
テーブルと結合しますcustomers.id
がorders.customers_id
と一致します。orders.product_id
が12345と一致します
- 左結合を使用しているため、左側のテーブル(
customers
)からの一致しない行を含め、右側のテーブル(orders
)からの列をNULL
値で埋めます。 SELECT
列指定にリストされている列のみを表示します。
結果は、結合されたすべての行が探している両方の条件に一致することです。ただし、左結合により、MySQLは結合条件を満たさなかった最初のテーブルからの行もすべて含めます。これにより、クエリの明白な意図に従っていないように見える「残り」の行が発生します。
2番目のクエリ(orders.product_id
= 12345)を結合条件として含める代わりに、WHERE
句に移動すると、異なる結果が得られます
SELECTcustomers.id AS customers_id,customers.name,orders.id AS orders_id,orders.product_idFROMcustomersLEFT JOINordersONcustomers.id = orders.customers_idWHEREorders.product_id = 12345;
今回は、3行のみが表示されます
+--------------+----------+-----------+------------+customers_id | name | orders_id | product_id |+--------------+----------+-----------+------------+4380 | Acme Co | 182 | 12345 |4380 | Acme Co | 480 | 12345 |320 | Other Co | 680 | 12345 |+--------------+----------+-----------+------------+3 rows in set (0.00 sec)
比較が実行される順序が、これらの違いの理由です。今回、MySQLは次のようにクエリを処理します
customers.id
がorders.customers_id
と一致するcustomers
テーブルの任意の行をorders
テーブルと結合します。- 左結合を使用しているため、左側のテーブル(
customers
)からの一致しない行を含め、右側のテーブル(orders
)からの列をNULL
値で埋めます。 WHERE
句を評価して、orders.product_id
列の値として12345を持たない行をすべて削除します。SELECT
列指定にリストされている列のみを表示します。
今回は、左結合を使用しているにもかかわらず、WHERE
句は正しいproduct_id
を持たないすべての行をフィルタリングすることにより、結果を切り捨てます。一致しない行はすべてproduct_id
がNULL
に設定されているため、これにより、左結合によって入力された一致しない行がすべて削除されます。また、この2回目のチェックに合格しなかった結合条件によって一致した行もすべて削除されます。
MySQLがクエリを実行するために使用する基本的なプロセスを理解することで、データを操作する際に発生しやすく、デバッグが難しい間違いを回避できます。
結論
この記事では、結合とは何か、およびMySQLが複数のテーブルからレコードを結合する方法として結合をどのように実装するかについて説明しました。利用可能なさまざまな種類の結合と、ON
やWHERE
句などのさまざまな条件が、データベースが結果を構築する方法にどのように影響するかについて説明しました。
結合に慣れてくると、さまざまなソースからデータを取得し、情報の断片をまとめてより完全な全体像を作成するためのツールキットの通常の部品として結合を使用できるようになります。結合は、組織の原則とパフォーマンスの考慮事項が分離する可能性のあるデータをまとめるのに役立ちます。結合を効果的に使用する方法を学ぶことは、システムでデータがどのように編成されているかに関係なく、データをまとめるのに役立ちます。