データベースにおけるジョインステートメントは、ジョイン条件に基づいてデータベース内の2つ以上のテーブルを結合するために使用されます。「ジョイン」によって生成された集合は、テーブルとして保存したり、テーブルとして使用したりできます。
ジョインステートメントの意味は、2つのテーブルのプロパティをそれぞれの値を通じて組み合わせることです。データベースにおけるジョインタイプには一般的に、内部結合(Inner-Join)、外部結合(Outer-Join)、セミ結合(Semi-Join)、およびアンチ結合(Anti-Join)が含まれます。そのうち、セミ結合とアンチ結合はどちらもサブクエリによって書き換えられるものであり、SQL自体にはアンチ結合やセミ結合の構文は記述されていません。
接続条件
接続条件は、等価結合(例:t1.a = t2.b)と不等価結合(例:t1.a < t2.b)に分けられます。不等価結合条件と比較して、等価結合条件の利点の一つは、データベースで効率的な結合アルゴリズム、例えばHash JoinやMerge-Sort Joinを使用できることです。
Self-Join
Self-Joinとは、テーブルが自身と結合するJoin操作を指します。以下にSelf-Joinの例を示します。
obclient> CREATE TABLE t1(a INT PRIMARY KEY, b INT, c INT);
Query OK, 0 rows affected
obclient> SELECT * FROM t1 AS ta, t1 AS tb WHERE ta.b = tb.b;
内部結合(Inner-Join)
内部結合は、データベースにおける最も基本的な結合操作です。内部結合は結合条件に基づいて、2つのテーブル(例えばAとB)の列を組み合わせ、新しい結果テーブルを生成します。クエリはAテーブルの各行とBテーブルの各行を比較し、結合条件を満たす組み合わせを見つけ出します。結合条件が満たされると、AとBのマッチする行は列ごとに組み合わせられ(並べて組み合わせられ)、結果セットの1行となります。結合によって生成される結果セットは、まず2つのテーブルに対してデカルト積(交差結合、Aの各行とBの各行を組み合わせる)を行い、次に結合条件を満たすレコードを返すことで定義できます。
obclient> CREATE TABLE t1(c1 INT,c2 INT);
Query OK, 0 rows affected
obclient> CREATE TABLE t2(c1 INT,c2 INT);
Query OK, 0 rows affected
obclient> SELECT * FROM t1 JOIN t2 USING(c1);
外部結合(Outer-Join)
外部結合では、結合される2つのテーブルの各レコードが相手方のテーブルにマッチするレコードを持つ必要はありません。すべてのレコードを保持する(そのレコードにマッチするものがない場合でも保持する)テーブルを保持テーブルと呼びます。
外部結合は、結合されるテーブルが左側のテーブル、右側のテーブル、または両方のテーブルの行を保持するかどうかに基づいて、左外部結合、右外部結合、完全結合にさらに分類されます。
左外部結合では、左側のテーブルの1行が右側のテーブルで見つからない場合、右側のテーブルに自動的に
NULLが補完されます。右外部結合では、右側のテーブルの1行が左側のテーブルで見つからない場合、左側のテーブルに自動的に
NULLが補完されます。完全結合とは、左側のテーブルまたは右側のテーブルでマッチする行が見つからない場合に自動的に
NULLが補完されることを指します。
obclient> CREATE TABLE t1(c1 INT,c2 INT);
Query OK, 0 rows affected
obclient> CREATE TABLE t2(c1 INT,c2 INT);
Query OK, 0 rows affected
obclient> SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1;
obclient> SELECT * FROM t1 RIGHT JOIN t2 ON t1.c1 = t2.c1;
obclient> SELECT * FROM t1 FULL JOIN t2 ON t1.c1 = t2.c1;
セミ結合(Semi-Join)
テーブルAとテーブルBをleft/right Semi-Joinする場合、テーブルAのすべての行またはテーブルBのすべての行が返されます。ただし、そのうち相手方のテーブルでマッチする行が存在する場合に限られます。
Semi-Joinはサブクエリを展開することでのみ実現可能です。以下に示します。
obclient> CREATE TABLE t1(a INT PRIMARY KEY, b INT, c INT);
Query OK, 0 rows affected
obclient> CREATE TABLE t2(a INT PRIMARY KEY, b INT, c INT);
Query OK, 0 rows affected
obclient> INSERT INTO t1 VALUES (1, 1, 1),(2, 2, 2);
obclient> INSERT INTO t2 VALUES (1, 1, 1),(2, 2, 2);
obclient> SELECT * FROM t1 WHERE t1.a IN (SELECT t2.b FROM t2 WHERE t2.c = t1.c);
EXPLAIN を使用してクエリ計画を確認すると、依存関係のあるサブクエリが展開され、Semi-Joinに書き換えられていることがわかります。
obclient> EXPLAIN SELECT * FROM t1 WHERE t1.a IN (SELECT t2.b FROM t2 WHERE t2.c = t1.c);
| ========================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
----------------------------------------
|0 |MERGE SEMI JOIN| |2 |76 |
|1 | TABLE SCAN |t1 |2 |37 |
|2 | SORT | |2 |38 |
|3 | TABLE SCAN |t2 |2 |37 |
========================================
...
アンチ結合(Anti-Join)
テーブルAとテーブルBを左結合または右結合でアンチ結合する場合、テーブルAのBテーブルにマッチする行が存在しないすべての行、またはテーブルBのAテーブルにマッチする行が存在しないすべての行のみが返されます。
Semi-Joinと同様に、アンチ結合もサブクエリを展開することでのみ実現できます。以下に示します。
obclient> CREATE TABLE t1(a INT PRIMARY KEY, b INT, c INT);
Query OK, 0 rows affected
obclient> CREATE TABLE t2(a INT PRIMARY KEY, b INT, c INT);
Query OK, 0 rows affected
obclient> INSERT INTO t1 VALUES (1, 1, 1),(2, 2, 2);
obclient> INSERT INTO t2 VALUES (1, 1, 1),(2, 2, 2);
obclient> SELECT * FROM t1 WHERE t1.a NOT IN (SELECT t2.b FROM t2 WHERE t2.c = t1.c);
EXPLAIN を使用してクエリ計画を確認すると、依存関係のあるサブクエリがアンチ結合に書き換えられていることがわかります。
obclient> EXPLAIN SELECT * FROM t1 WHERE t1.a NOT IN (SELECT t2.b FROM t2 WHERE t2.c = t1.c);
| =============================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |HASH RIGHT ANTI JOIN| |0 |77 |
|1 | TABLE SCAN |t2 |2 |37 |
|2 | TABLE SCAN |t1 |2 |37 |
=============================================
...