データベースにおける結合ステートメントは、結合条件に基づいてデータベース内の2つ以上のテーブルを組み合わせるために使用されます。「結合」によって生成された集合は、テーブルとして保存したり、テーブルのように使用したりできます。
結合ステートメントの意味は、2つのテーブルの属性をそれぞれの値によって組み合わせることです。データベースにおける結合タイプには、通常、内部結合(Inner-Join)、外部結合(Outer-Join)、半結合(Semi-Join)、および反結合(Anti-Join)が含まれます。その中で、Semi-JoinとAnti-Joinはサブクエリによる書き換えで得られるものであり、SQL自体にはAnti-JoinやSemi-Joinの構文は記述されていません。
結合条件
結合条件は、等価結合(例: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)
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テーブルでマッチする行、または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 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と同様に、Anti-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 を使用してクエリプランを確認すると、依存関係のあるサブクエリがAnti-Joinに書き換えられているのが確認できます。
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 |
=============================================
...