共有

はじめに

関連データを別々のテーブルに分割することは、一貫性、柔軟性、および特定の種類のパフォーマンスの観点から有益です。ただし、関連情報が複数のテーブルにまたがる場合でも、レコードを合理的な方法で再統合する必要があります。

リレーショナルデータベースでは、結合(joins)は、共通のフィールド値に基づいて2つ以上のテーブルのレコードを組み合わせる方法を提供します。異なる種類の結合は、一致しない行をどのように処理するかによって、異なる結果を達成できます。このガイドでは、PostgreSQLが提供するさまざまな種類の結合と、それらを使用して複数のソースからのテーブルデータを結合する方法について説明します。

結合とは?

簡単に言うと、結合(joins)は複数のテーブルからデータを表示する方法です。これらは、特定の列で値が一致することに基づいて、異なるソースからのレコードを結合することで実現します。結果として得られる各行は、最初のテーブルのレコードと2番目のテーブルの行を、各テーブルの1つ以上の列が同じ値を持つことに基づいて組み合わせたもので構成されます。

結合の基本的な構文は次のようになります

SELECT
*
FROM
<first_table>
<join_type> <second_table>
<join_condition>;

結合では、結果として得られる各行は、最初のテーブルのすべての列に続いて2番目のテーブルのすべての列を含めることによって構築されます。クエリのSELECT部分は、表示したい正確な列を指定するために使用できます。

比較に使用される列の値が一意でない場合、元のテーブルから複数の行が構築されることがあります。たとえば、最初のテーブルから比較される列に「赤」という値を持つ2つのレコードがあるとします。これに一致する2番目のテーブルの列には、その値を持つ3つの行があります。この結合は、達成可能なさまざまな組み合わせを表す、その値に対して6つの異なる行を生成します。

結合の種類と結合条件は、表示される各行がどのように構築されるかを決定します。これは、結合条件に一致する行と一致しない行が各テーブルからどのように扱われるかに影響します。

便宜上、多くの結合では、一方のテーブルの主キーと、もう一方のテーブルの関連する外部キーが一致します。主キーと外部キーはデータベースシステムによって一貫性保証を維持するためにのみ使用されますが、その関係により、結合条件の良い候補となることがよくあります。

異なる種類の結合

さまざまな種類の結合が利用可能であり、それぞれが異なる結果を生み出す可能性があります。各タイプがどのように構築されるかを理解することは、異なるシナリオにどれが適切かを判断するのに役立ちます。

内部結合

デフォルトの結合は内部結合(inner join)と呼ばれます。PostgreSQLでは、これはINNER JOINまたは単にJOINを使用して指定できます。

内部結合の構文を示す典型的な例を以下に示します

SELECT
*
FROM
table_1
[INNER] JOIN table_2
ON table_1.id = table_2.table_1_id;

内部結合は、各テーブルからの行を結合することによって作成された行のみを表示するため、最も制約の厳しい結合タイプです。構成テーブル内で、他のテーブルに一致する対応する行がなかった行は、結果から削除されます。たとえば、最初のテーブルの比較列に「青」という値があり、2番目のテーブルにその値を持つレコードがない場合、その行は出力から抑制されます。

結果を構成テーブルのベン図として表すと、内部結合は2つの円の重なり合う領域を表すことができます。いずれかのテーブルにのみ存在した値は表示されません。

左外部結合

左外部結合(left join)は、内部結合で見つかったすべてのレコードに加えて、最初のテーブルからの一致しないすべての行を表示する結合です。PostgreSQLでは、これはLEFT OUTER JOINまたは単にLEFT JOINとして指定できます。

左外部結合の基本的な構文は次のパターンに従います

SELECT
*
FROM
table_1
LEFT JOIN table_2
ON table_1.id = table_2.table_1_id;

左外部結合は、まず内部結合を実行して両方のテーブルの一致するすべてのレコードから行を構築することによって構築されます。その後、最初のテーブルからの一致しないレコードも含まれます。結合の各行には両方のテーブルの列が含まれるため、一致しない列には2番目のテーブルのすべての列の値としてNULLが使用されます。

結果を構成テーブルのベン図として表すと、左外部結合は左側の円全体を表すことができます。2つの円の交差部分で表される左側の円の部分には、右側のテーブルによって補足された追加データが含まれます。

右外部結合

右外部結合(right join)は、内部結合で見つかったすべてのレコードに加えて、2番目のテーブルからの一致しないすべての行を表示する結合です。PostgreSQLでは、これはRIGHT OUTER JOINまたは単にRIGHT JOINとして指定できます。

右外部結合の基本的な構文は次のパターンに従います

SELECT
*
FROM
table_1
RIGHT JOIN table_2
ON table_1.id = table_2.table_1_id;

右外部結合は、まず内部結合を実行して両方のテーブルの一致するすべてのレコードから行を構築することによって構築されます。その後、2番目のテーブルからの一致しないレコードも含まれます。結合の各行には両方のテーブルの列が含まれるため、一致しない列には最初のテーブルのすべての列の値としてNULLが使用されます。

結果を構成テーブルのベン図として表すと、右外部結合は右側の円全体を表すことができます。2つの円の交差部分で表される右側の円の部分には、左側のテーブルによって補足された追加データが含まれます。

完全外部結合

完全結合(full join)は、内部結合で見つかったすべてのレコードに加えて、両方の構成テーブルからの一致しないすべての行を表示する結合です。PostgreSQLでは、これはFULL OUTER JOINまたは単にFULL JOINとして指定できます。

完全外部結合の基本的な構文は次のパターンに従います

SELECT
*
FROM
table_1
FULL JOIN table_2
ON table_1.id = table_2.table_1_id;

完全外部結合は、まず内部結合を実行して両方のテーブルの一致するすべてのレコードから行を構築することによって構築されます。その後、両方のテーブルからの一致しないレコードも含まれます。結合の各行には両方のテーブルの列が含まれるため、一致しない列には、一致しない他のテーブルのすべての列の値としてNULLが使用されます。

結果を構成テーブルのベン図として表すと、完全結合は両方の構成円全体を表すことができます。2つの円の交差部分には、各構成テーブルによって提供される値が含まれます。重なり合う領域の外側の円の部分には、それらが属するテーブルからの値が含まれ、他のテーブルで見つかった列を埋めるためにNULLが使用されます。

クロス結合

CROSS JOINと呼ばれる特別な結合も利用できます。クロス結合は、各テーブルの行が互いに一致するかどうかを判断するためにいかなる比較も使用しません。代わりに、最初のテーブルの各行を2番目のテーブルの各行に単純に追加することによって結果が構築されます。

これは、2つ以上のテーブルの行の直積を生成します。実際には、この結合スタイルは各テーブルからの行を無条件に結合します。したがって、各テーブルに3つの行がある場合、結果のテーブルには両方のテーブルのすべての列を含む9つの行が作成されます。

たとえば、t1というテーブルがt2というテーブルと結合されており、それぞれがr1r2r3という行を持っている場合、結果は次のように結合された9つの行になります

t1.r1 + t2.r1
t1.r1 + t2.r2
t1.r1 + t2.r3
t1.r2 + t2.r1
t1.r2 + t2.r2
t1.r2 + t2.r3
t1.r3 + t2.r1
t1.r3 + t2.r2
t1.r3 + t2.r3

自己結合

自己結合とは、テーブルの行自体と結合するあらゆる結合のことです。これがどのように役立つかはすぐには明らかではないかもしれませんが、実際には多くの一般的なアプリケーションがあります。

多くの場合、テーブルは互いに関連して複数の役割を果たすことができるエンティティを記述します。たとえば、peopleテーブルがある場合、各行にはテーブル内の他のpeopleを参照するmother列が含まれる可能性があります。自己結合を使用すると、これらの値が一致する最初のテーブルに2番目のインスタンスのテーブルを結合することで、これらの異なる行を結合できます。

自己結合は同じテーブルを2回参照するため、参照を明確にするためにテーブルエイリアスが必要です。上記の例では、たとえば、peopleテーブルの2つのインスタンスをpeople AS childrenpeople AS mothersというエイリアスを使用して結合できます。これにより、結合条件を定義するときに、参照しているテーブルのインスタンスを指定できます。

従業員と管理者の関係を示す別の例を以下に示します

SELECT
*
FROM
people AS employee
JOIN people AS manager
ON employee.manager_id = manager.id;

結合条件

テーブルを結合するとき、結合条件は、複合結果を形成するために行がどのように照合されるかを決定します。基本的な前提は、その行で結合が発生するために一致する必要がある各テーブルの列を定義することです。

ON

テーブル結合の条件を定義する最も標準的な方法は、ON句を使用することです。ON句は、結合が発生するタイミングを決定するために比較される各テーブルの正確な列を指定するために、等号を使用します。PostgreSQLは提供された列を使用して、各テーブルからの行を結合します。

ON句は最も冗長ですが、利用可能な結合条件の中で最も柔軟でもあります。これは、結合される各テーブルの列名がどれほど標準化されているかに関係なく、特定の指定を可能にします。

ON句の基本的な構文は次のようになります

SELECT
*
FROM
table1
JOIN
table2
ON
table1.id = table2.ident;

ここでは、table1id列がtable2ident列と一致する場合に、table1table2の行が結合されます。内部結合が使用されているため、結果には結合された行のみが表示されます。クエリはワイルドカード*文字を使用しているため、両方のテーブルのすべての列が表示されます。

これは、結合条件を満たすことで同じ厳密な値を持つにもかかわらず、table1id列とtable2ident列の両方が表示されることを意味します。SELECT列リストで表示したい正確な列を指定することで、この重複を避けることができます。

USING

USING句は、比較される列が両方のテーブルで同じ名前を持つ場合に使用できるON句の条件を指定するための省略形です。USING句は、比較されるべき共有列名のリストを括弧で囲んで取ります。

USING句の一般的な構文は次の形式を使用します

SELECT
*
FROM
table1
JOIN
table2
USING
(id, state);

この結合は、両方のテーブルが共有する2つの列(idstate)がそれぞれ一致する値を持つ場合に、table1table2と結合します。

この同じ結合は、ONを使用して次のように冗長に表現することもできます

SELECT
*
FROM
table1
JOIN
table2
ON
table1.id = table2.id AND table1.state = table2.state;

上記のどちらの結合も同じデータを持つ同じ行を構築する結果になりますが、表示方法はわずかに異なります。ON句は両方のテーブルのすべての列を含みますが、USING句は重複する列を抑制します。したがって、2つの別々のid列と2つの別々のstate列(各テーブルに1つずつ)がある代わりに、結果は共有された列がそれぞれ1つだけになり、それに続いてtable1table2によって提供される他のすべての列が表示されます。

NATURAL

NATURAL句は、USING句の冗長性をさらに減らすことができる別の省略形です。NATURAL結合は、一致させる列を一切指定しません。代わりに、PostgreSQLは各データベースで一致する列を持つすべての列に基づいて、テーブルを自動的に結合します。

NATURAL結合句の一般的な構文は次のようになります

SELECT
*
FROM
table1
NATURAL JOIN
table2;

table1table2の両方にidstatecompanyという名前の列があると仮定すると、上記のクエリはON句を使用した次のクエリと同等になります

SELECT
*
FROM
table1
JOIN
table2
ON
table1.id = table2.id AND table1.state = table2.state AND table1.company = table2.company;

そして、USING句を使用したこのクエリ

SELECT
*
FROM
table1
JOIN
table2
USING
(id, state, company);

USING句と同様に、NATURAL句は重複する列を抑制するため、結果には結合された各列の単一のインスタンスのみが存在します。

NATURAL句はクエリの冗長性を減らすことができますが、使用する際には注意が必要です。テーブルを結合するために使用される列は自動的に計算されるため、構成テーブルの列が変更されると、新しい結合条件により結果が大きく異なる可能性があります。

結合条件とWHERE

結合条件は、WHERE句を使用してデータの行をフィルタリングするために使用される比較と多くの特性を共有しています。どちらの構造も、行が考慮されるためにtrueと評価されなければならない式を定義します。このため、追加の比較をWHERE構造に含めることと、結合句自体の中で定義することの違いが常に直感的であるとは限りません。

結果として生じる違いを理解するためには、PostgreSQLがクエリの異なる部分を処理する順序を見る必要があります。この場合、結合条件内の述語が最初に処理され、メモリ内の仮想結合テーブルが構築されます。この段階の後、WHERE句内の式が評価され、結果の行がフィルタリングされます。

例として、結合する必要があるcustomerorderという2つのテーブルがあるとします。customer.id列とorder.customer_id列を一致させることで、2つのテーブルを結合したいと考えています。さらに、product_idが12345であるorderテーブルの行に関心があります。

上記の要件を考えると、私たちが関心を持つ条件は2つあります。しかし、これらの条件をどのように表現するかによって、得られる結果は異なります。

まず、両方をLEFT JOINの結合条件として使用してみましょう

SELECT
customer.id AS customer_id,
customer.name,
order.id AS order_id,
order.product_id
FROM
customer
LEFT JOIN
order
ON
customer.id = order.customer_id AND order.product_id = 12345;

結果は次のようなものになる可能性があります

customer_id | name | order_id | product_id
------------+----------+----------+------------
4380 | Acme Co | 480 | 12345
4380 | Acme Co | 182 | 12345
320 | Other Co | 680 | 12345
4380 | Acme Co | |
320 | Other Co | |
20 | Early Co | |
8033 | Big Co | |
(7 rows)

PostgreSQLは次の操作を実行してこの結果に到達しました

  1. customerテーブルの任意の行をorderテーブルと結合し、その条件は
    • customer.idorder.customer_idと一致する。
    • order.product_idが12345と一致する
  2. 左結合を使用しているため、左テーブル(customer)からの一致しない行もすべて含め、右テーブル(order)の列にはNULL値を埋めます。
  3. SELECT列指定にリストされている列のみを表示します。

結果として、結合された行はすべて、私たちが求めている両方の条件に一致します。しかし、左結合はPostgreSQLに、結合条件を満たさなかった最初のテーブルからの行も含むようにさせます。これにより、クエリの明らかな意図に従っていないように見える「残りの」行が生じます。

2番目のクエリ(order.product_id = 12345)を結合条件として含めるのではなく、WHERE句に移動すると、異なる結果が得られます

SELECT
customer.id AS customer_id,
customer.name,
order.id AS order_id,
order.product_id
FROM
customer
LEFT JOIN
order
ON
customer.id = order.customer_id
WHERE
order.product_id = 12345;

今回は、3行のみが表示されます

customer_id | name | order_id | product_id
------------+----------+----------+------------
4380 | Acme Co | 480 | 12345
4380 | Acme Co | 182 | 12345
320 | Other Co | 680 | 12345
(3 rows)

比較が実行される順序がこれらの違いの理由です。今回は、PostgreSQLは次のようにクエリを処理します

  1. customerテーブルの任意の行をorderテーブルと結合し、その条件はcustomer.idorder.customer_idと一致する。
  2. 左結合を使用しているため、左テーブル(customer)からの一致しない行もすべて含め、右テーブル(order)の列にはNULL値を埋めます。
  3. WHERE句を評価して、order.product_id列の値が12345でないすべての行を削除します。
  4. SELECT列指定にリストされている列のみを表示します。

今回は、左結合を使用しているにもかかわらず、WHERE句が正しいproduct_idを持たないすべての行をフィルタリングすることで結果を切り詰めます。一致しない行はすべてproduct_idNULLに設定されるため、これにより左結合によって生成された一致しない行はすべて削除されます。また、結合条件によって一致したが、この2回目のチェックに合格しなかった行もすべて削除されます。

PostgreSQLがクエリを実行するために使用する基本的なプロセスを理解することは、データを操作する際に、簡単に犯してしまうがデバッグが難しい間違いを避けるのに役立ちます。

まとめ

このガイドでは、結合がリレーショナルデータベースで異なるテーブルからのデータを組み合わせて、より価値のある回答を提供する方法について説明しました。PostgreSQLがサポートするさまざまな結合、各タイプが結果を組み立てる方法、および特定の種類の結合を使用する際に何を期待すべきかについて話しました。その後、結合条件を定義するさまざまな方法を検討し、結合とWHERE句の相互作用がどのように予期せぬ結果につながるかを見ました。

結合は、リレーショナルデータベースを非常に多くの異なる種類のクエリを処理できるほど強力で柔軟にするための不可欠な部分です。論理的な境界を使用してデータを整理しつつ、ケースバイケースでデータを新しい方法で再結合できることは、PostgreSQLのようなリレーショナルデータベースに驚くべき多様性をもたらします。テーブル間のこの結合方法を学ぶことで、より複雑なクエリを作成し、データベースにデータの完全な全体像を作成させることに頼ることができるようになります。

よくある質問

はい、PostgreSQLは外部結合をサポートしています。例えば、以下のようにLEFT OUTER JOINまたは単にLEFT JOINを使用できます

SELECT
*
FROM
table_1
LEFT JOIN table_2
ON table_1.id = table_2.table_1_id;

PostgreSQLLATERALキーワードは、副SELECT FROM項目の前に置くことができ、副SELECTがFROMリストのそれ以前に現れるFROM項目の列を参照することを許可します。(LATERALなしでは、各副SELECTは独立して評価されるため、他のFROM項目を相互参照することはできません。)

はい、PostgreSQLでCROSS JOINを実行できます。構文は次のようになります

SELECT select_list
FROM t1
CROSS JOIN t2;

上記の例は、この出力例のように表示されます

はい、PostgreSQLは完全結合をサポートしています。FULL OUTER JOINまたはFULL JOINとして指定できます。

構文は次のようになります

SELECT
*
FROM
table_1
FULL JOIN table_2
ON table_1.id = table_2.table_1_id;

PostgreSQLのデフォルトの結合は内部結合であり、INNER JOINまたは単にJOINを使用することで指定できます。

構文は

SELECT
*
FROM
table_1
[INNER] JOIN table_2
ON table_1.id = table_2.table_1_id;
著者について
Justin Ellingwood

ジャスティン・エリングウッド

ジャスティンは2013年以来、データベース、Linux、インフラストラクチャ、および開発者ツールについて執筆しています。彼は現在、妻と2匹のウサギとベルリンに住んでいます。彼は通常、三人称で書く必要がないため、関係者全員にとって安心です。
© . All rights reserved.