はじめに
パフォーマンスと整合性の目的でデータを個別のテーブルに分けることがしばしば有用である一方で、特定の要求に応えるためには複数のテーブルからデータを参照する必要があることがよくあります。テーブルを結合することは、共通のフィールド値に基づいて各レコードを照合することで、様々なテーブルのデータを組み合わせる方法です。
結合にはいくつかの異なる種類があり、それぞれテーブルレコードを組み合わせる様々な方法を提供します。この記事では、MySQLが結合をどのように実装しているか、そしてそれぞれの結合がどのようなシナリオで最も有用であるかについて説明します。
結合とは?
簡単に言えば、結合は複数のテーブルからのデータを表示する方法です。特定の列の値が一致することに基づいて、異なるソースからのレコードをつなぎ合わせることによってこれを行います。結果として得られる各行は、最初のテーブルのレコードと、各テーブルの1つまたは複数の列が同じ値を持つことに基づいて、2番目のテーブルの行が組み合わされたもので構成されます。
結合の基本的な構文は次のようになります。
SELECT*FROM<first_table><join_type> <second_table><join_condition>;
結合では、結果として得られる各行は、最初のテーブルのすべての列の後に2番目のテーブルのすべての列を含めることによって構築されます。クエリのSELECT
部分を使用して、表示したい正確な列を指定できます。
比較に使用される列の値が一意でない場合、元のテーブルから複数の行が構築されることがあります。たとえば、最初のテーブルで「赤」の値を持つ2つのレコードがある列を比較していると想像してください。これと照合される2番目のテーブルには、その値を持つ3つの行があります。結合は、その値に対して、達成可能な様々な組み合わせを表す6つの異なる行を生成します。
結合の種類と結合条件は、表示される各行がどのように構築されるかを決定します。これは、結合条件で一致する行と一致しない行が各テーブルからどうなるかに影響します。
便宜上、多くの結合は、一方のテーブルの主キーと、もう一方のテーブルの関連する外部キーを一致させます。主キーと外部キーはデータベースシステムによって整合性保証を維持するためにのみ使用されますが、その関係性から、結合条件の良い候補となることがよくあります。
異なる種類の結合
利用可能な結合には様々な種類があり、それぞれが異なる結果を生成する可能性があります。各タイプがどのように構築されるかを理解することで、異なるシナリオにどのタイプが適切かを判断するのに役立ちます。
内部結合と交差結合
デフォルトの結合は内部結合と呼ばれます。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;
内部結合は最も制約の厳しい結合タイプであり、両方のテーブルからの行を結合することによって作成された行のみを表示します。構成テーブル内の行で、他のテーブルに対応する一致する要素がなかったものは、結果から削除されます。たとえば、最初のテーブルの比較列に「青」の値があり、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
テーブルの2つのインスタンスをpeople AS children
とpeople AS mothers
というエイリアスを使用して結合できます。これにより、結合条件を定義するときに、どのテーブルインスタンスを参照しているかを指定できます。
もう一つの例を挙げます。今度は従業員とマネージャーの関係を表しています。
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
句の冗長性をさらに減らすことができるもう一つの省略形です。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
句を使用してデータの行をフィルタリングするために使用される比較と多くの特徴を共有しています。どちらの構成要素も、行が考慮されるために真と評価されなければならない式を定義します。このため、追加の比較を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
句のような異なる条件がデータベースが結果を構築する方法にどのように影響するかを説明しました。
結合に慣れるにつれて、さまざまなソースからデータを引き出し、情報の断片を結合してより完全な全体像を作成するためのツールキットの通常の機能として使用できるようになるでしょう。結合は、組織の原則やパフォーマンスの考慮事項によって分離されている可能性のあるデータをまとめるのに役立ちます。結合を効果的に使用する方法を学ぶことで、システムでデータがどのように整理されているかに関係なく、データをまとめることができます。