(2026年7月、内容を全面的に更新)
SQL でテーブルを結合する JOIN には、INNER JOIN・LEFT OUTER JOIN・RIGHT OUTER JOIN・FULL OUTER JOIN・CROSS JOIN の5種類があります。違いは一言でいえば「結合相手が見つからなかった行をどう扱うか」です。INNER は捨てます。LEFT/RIGHT/FULL は残して NULL で埋めます。CROSS はそもそも条件を付けず、全組み合わせを作ります。
答えとしてはこれで全部です。ただ、この一言だけを頼りに書いた SQL は、たいてい「思ったより行が少ない」「NULL の行はどこへ消えた」という形でつまずきます。そこでこの記事では、結合相手のいない行をわざと混ぜたサンプルデータに5種類の JOIN を順にかけて、結果がどう変わるかを目で確認していきます。あわせて結合条件の書き方(ON/USING/NATURAL)の違い、複数 JOIN の評価順、データベースごとの対応差(MySQL に FULL OUTER JOIN がない、など)もまとめます。
サンプルデータ
社員テーブル emp と部署テーブル dept を使います。以降の実行結果はすべてこの2表に対するものです(PostgreSQL 17 で実行。ほかの DB での差異は後述の対応表にまとめます)。
CREATE TABLE emp (id INT, name VARCHAR(20), dept_id INT); CREATE TABLE dept (dept_id INT, dept_name VARCHAR(20)); INSERT INTO emp VALUES (1,'佐藤',10),(2,'鈴木',20),(3,'高橋',NULL),(4,'田中',40); INSERT INTO dept VALUES (10,'営業部'),(20,'開発部'),(30,'総務部');
わざと「はぐれ者」を混ぜてあります。高橋さんは部署未所属(dept_id が NULL)、田中さんの dept_id 40 は dept 側に存在せず、総務部(30)には所属する社員がいません。この3つの扱いが JOIN の種類ごとに変わります。
JOIN の種類と結果の違い
INNER JOIN(内部結合)
結合条件を満たす行の組だけを返します。どちらか片方にしかない行は結果から消えます。
SELECT * FROM emp INNER JOIN dept USING(dept_id);
| dept_id | id | name | dept_name |
|---|---|---|---|
| 10 | 1 | 佐藤 | 営業部 |
| 20 | 2 | 鈴木 | 開発部 |
結果は2行です。高橋(NULL)・田中(相手なし)・総務部(社員なし)はすべて消えました。「両方にあるものだけ」が INNER JOIN です。
なお初版のこの記事では INNER JOIN を「内積」と説明していましたが、これは用語の誤りでした。動きとしては「2表の全組み合わせ(直積)から、条件に合う組だけを残す」と考えるのが正確です。
LEFT OUTER JOIN(左外部結合)
左のテーブル(emp)の行は、結合相手がいなくても全部残します。相手がいない行は、右テーブル由来の列が NULL で埋められます。
SELECT * FROM emp LEFT JOIN dept USING(dept_id);
| dept_id | id | name | dept_name |
|---|---|---|---|
| 10 | 1 | 佐藤 | 営業部 |
| 20 | 2 | 鈴木 | 開発部 |
| NULL | 3 | 高橋 | NULL |
| 40 | 4 | 田中 | NULL |
4行になりました。高橋と田中も残り、dept_name が NULL になっています。「左は全員出席、右は来られる人だけ」というイメージです。
INNER と LEFT のどちらを使うべきか、また条件を ON に書くか WHERE に書くかで結果が変わる話は、INNER JOIN と LEFT JOIN の違い — 結果がどう変わるか実例で確認 で詳しく扱っています。
RIGHT OUTER JOIN(右外部結合)
LEFT の左右を入れ替えたものです。今度は右のテーブル(dept)の行が全部残ります。
SELECT * FROM emp RIGHT JOIN dept USING(dept_id);
| dept_id | id | name | dept_name |
|---|---|---|---|
| 10 | 1 | 佐藤 | 営業部 |
| 20 | 2 | 鈴木 | 開発部 |
| 30 | NULL | NULL | 総務部 |
3行です。社員のいない総務部が、id と name を NULL にして現れました。emp RIGHT JOIN dept は dept LEFT JOIN emp と同じ行を返すので(SELECT * での列の並び順は変わります)、実務では LEFT に書き換えて統一してしまうことが多いです。
FULL OUTER JOIN(完全外部結合)
LEFT と RIGHT を合わせたもので、両方のテーブルの行をすべて残します。
SELECT * FROM emp FULL OUTER JOIN dept USING(dept_id);
| dept_id | id | name | dept_name |
|---|---|---|---|
| 10 | 1 | 佐藤 | 営業部 |
| 20 | 2 | 鈴木 | 開発部 |
| NULL | 3 | 高橋 | NULL |
| 40 | 4 | 田中 | NULL |
| 30 | NULL | NULL | 総務部 |
5行(LEFT の4行+総務部)です。2つのデータの突き合わせで「どちらか片方にしかないものも漏らさず見たい」ときに使います。ただし MySQL は FULL OUTER JOIN に対応していません(代替は後述)。
CROSS JOIN(交差結合)
結合条件なしで、左右の全行の組み合わせ(直積)を返します。
SELECT COUNT(*) FROM emp CROSS JOIN dept; -- → 12(4行 × 3行)
emp 4行 × dept 3行 = 12行です。組み合わせ表を作りたい場合以外で意図して使うことはまれで、むしろ「結合条件を書き忘れた事故」の結果として目にすることが多い結合です。
省略形の対応
普段よく見る JOIN や LEFT JOIN は省略形です。対応は次のとおりで、PostgreSQL 17 / MySQL 8.4 / SQLite 3.51 で正式形と結果が一致することを確認しています(Oracle でも JOIN = INNER JOIN を確認済み)。
| 省略形 | 正式な書き方 |
|---|---|
| JOIN | INNER JOIN |
| LEFT JOIN | LEFT OUTER JOIN |
| RIGHT JOIN | RIGHT OUTER JOIN |
| FULL JOIN | FULL OUTER JOIN |
※ FULL JOIN の行だけは、FULL OUTER JOIN に対応する DB のうち PostgreSQL/SQLite で確認しています。MySQL は FULL 結合自体に対応していません(後述)。
つまり INNER と OUTER のキーワードは付けても付けなくても意味が変わりません。カンマ区切りの古い書き方(FROM emp, dept WHERE …)との関係や、JOIN と混在させるとエラーになる罠も含めて、SQL の JOIN は省略できる — INNER/OUTER キーワードとカンマ結合の整理 に整理しました。
ON, USING, NATURAL による結合条件の指定
CROSS JOIN 以外の JOIN には結合条件を指定します。書き方は3通りあります。
ON は最も汎用的な書き方で、WHERE と同じ形式の条件式を書きます。列名が左右で違っていても、等号以外の条件でも書けます。
SELECT * FROM emp JOIN dept ON emp.dept_id = dept.dept_id;
USING は「両テーブルで同名の列」で等結合する場合の短縮形です。
SELECT * FROM emp JOIN dept USING(dept_id);
ON との見た目以上の違いとして、USING は結合列を1つにマージします。上の ON の例では結果に emp.dept_id と dept.dept_id の2列が現れますが、USING(dept_id) では dept_id は1回だけ(先頭に)現れます。この記事の結果表で dept_id が先頭に1列だけあったのはこのためです(PostgreSQL/MySQL/SQLite で共通の挙動です)。
NATURAL は「両テーブルの同名列すべて」を自動的に USING に指定したのと同じです。
SELECT * FROM emp NATURAL JOIN dept; -- → INNER JOIN USING(dept_id) と同じ2行
emp と dept の共通列は dept_id だけなので、この例では USING(dept_id) と同じ結果になります。ただし NATURAL は「たまたま同名の列」まで勝手に結合条件へ取り込むため、テーブル定義の変更で意味が変わってしまいます。動きを理解しておく価値はありますが、書くなら ON か USING をおすすめします。
複数の JOIN を組み合わせる
JOIN は3表以上つなげられます。カッコがなければ左から順に結合されます。この節の SQL は書き方(構文と評価順)の説明用で、3表目の loc は冒頭のサンプルデータにはない架空のテーブルです。
SELECT * FROM emp JOIN dept USING(dept_id) JOIN loc USING(loc_id);
この場合、まず emp と dept が結合され、その結果に loc が結合されます。順序を変えたいときはカッコで囲みます。
SELECT * FROM emp e LEFT JOIN (dept d JOIN loc l USING(loc_id)) ON e.dept_id = d.dept_id;
こう書くと dept と loc の INNER JOIN が先に評価され、その結果に対して emp を LEFT JOIN します。外部結合が混ざるときはこの順序で結果が変わることがあるので、意図がある場合はカッコで明示しておくと安全です。
データベースごとの対応差
SQL 標準の範囲でも、実装によって使える構文に差があります。今回実測した4系での対応は次のとおりです。
| PostgreSQL 17 | MySQL 8.4 | SQLite 3.51 | Oracle 23ai | |
|---|---|---|---|---|
| INNER/LEFT JOIN | ○ | ○ | ○ | ○ |
| RIGHT JOIN | ○ | ○ | ○(3.39.0から) | ○ |
| FULL OUTER JOIN | ○ | × | ○(3.39.0から) | ○ |
| CROSS JOIN に ON/USING | ×(構文エラー) | ○ | ○ | ×(エラー) |
補足をいくつか。
SQLite の RIGHT JOIN と FULL OUTER JOIN は 3.39.0(2022年6月)で追加されました。それより古い SQLite では使えません。また SQLite の CROSS JOIN には少し特殊な役割があり、オプティマイザがテーブルの結合順序を並べ替えなくなります(結合順を固定したいときの手段として公式に案内されています)。
CROSS JOIN に ON や USING を付けられるかは実装で割れています。MySQL はマニュアルに明記があるとおり JOIN / CROSS JOIN / INNER JOIN を構文上同義に扱うため CROSS JOIN … ON も通りますが(SQLite も同様に通ります)、PostgreSQL と Oracle では構文エラーです。じつは初版のこの記事には「PostgreSQL や MySQL では CROSS JOIN に ON, USING を指定すると INNER JOIN と等価」と書いていました。今回 PostgreSQL 17 で実行し直して、エラーになることを確認しています。19年近く、半分誤った記述を載せていたことになります。移植性を考えると、条件を付けるなら素直に INNER JOIN と書くのが無難です。
MySQL で FULL OUTER JOIN 相当の結果が欲しい場合は、LEFT JOIN と RIGHT JOIN を UNION ALL で合成します。
SELECT id, name, dept_id, dept_name FROM emp LEFT JOIN dept USING(dept_id) UNION ALL SELECT id, name, dept_id, dept_name FROM emp RIGHT JOIN dept USING(dept_id) WHERE emp.id IS NULL;
MySQL 8.4 で実行すると、前述の FULL OUTER JOIN と同じ5行が得られます。2つ目の SELECT の WHERE emp.id IS NULL で「右側にしかない行」(総務部)だけを足すのがポイントです。なお列は USING でマージされた dept_id を選んでください。ここで dept.dept_id を選ぶと、dept 側に存在しない田中の dept_id 40 が NULL になってしまいます(実際にやってみて気づいた罠です)。
Oracle には ANSI の JOIN 構文が入る前の独自記法(カンマ結合+ (+) 演算子)があり、古いコードでは今もよく見かけます。旧記法と ANSI 構文の対応は Oracle の JOIN の書き方 — 旧記法 (+) と ANSI 構文の対応表 にまとめました。
さいごに
JOIN の種類は「結合相手がいない行をどうするか」で選びます。部署のない高橋さんと、部署番号だけが宙に浮いた田中さんと、社員のいない総務部をどうしたいか、と言い換えてもかまいません。両方にある行だけでよければ INNER、片側を全部残すなら LEFT(または RIGHT)、両側とも残すなら FULL OUTER です。FULL OUTER が使えない MySQL では UNION ALL で合成します。結合条件は ON が基本で、同名列の等結合なら USING(結果の列が1つにマージされる)も便利です。
あわせて読みたい:
- SQL の JOIN は省略できる — INNER/OUTER キーワードとカンマ結合の整理
- INNER JOIN と LEFT JOIN の違い — 結果がどう変わるか実例で確認
- Oracle の JOIN の書き方 — 旧記法 (+) と ANSI 構文の対応表
(以下旧記事です)
MySQL / PostgreSQL / Oracle すべてのSQLサーバで基準になっているSQL標準のJOIN(LEFT JOINなど)について、基礎からしっかりまとめてみました。
■目次
■JOINの種類
SQL 標準では JOIN 句による結合構文は次のような種類があります。
INNER JOIN
LEFT OUTER JOIN
RIGHT OUTER JOIN
CROSS JOIN
LEFT JOIN, RIGHT JOIN など、よく使われる構文は上記の省略形です。
・ただの JOIN は INNER JOIN の省略形。
・LEFT JOIN は LEFT OUTER JOIN の省略形。
・RIGHT JOIN は RIGHT OUTER JOIN の省略形。
それぞれの JOIN 構文の意味は次のとおりです。
INNER JOIN: 内部結合
指定したカラムについて同じ値を持つレコード同士を結びつける。
内積なので指定したカラムの値がどちらかにしかないレコードについては結果に含まれません。
SELECT * FROM table1 INNER JOIN table2 USING(id)
は、次の SQL 文と同じ結果になります。
SELECT * FROM table1, table2 WHERE table1.id = table2.id
LEFT OUTER JOIN: 外部結合
左のテーブルを基準にして、指定したカラムについて同じ値を持つレコード同士を結びつける。
値が右のテーブルにあり左のテーブルにない場合は INNER JOIN 同様結果に含まれませんが、
値が左のテーブルにあり右のテーブルにない場合は INNER JOIN と異なり 右のテーブルのカラムには全て NULL がパディングされ、結果に含まれます。
このため、
SELECT * FROM table1 LEFT OUTER JOIN table2 USING(id) WHERE table2.id IS NOT NULL
は、
SELECT * FROM table1 INNER JOIN table2 USING(id)
と同じ結果になります。
RIGHT OUTER JOIN: 外部結合
LEFT OUTER JOIN の左右の意味を入れ換えたもの。
SELECT * FROM table1 RIGHT OUTER JOIN table2 USING(id)
は
SELECT * FROM table2 LEFT OUTER JOIN table1 USING(id)
と同じ結果になります。
CROSS JOIN: 交差結合
ON, USING で条件を指定しない場合、左右のテーブルの直積を返します。
つまり、左右のテーブルにそれぞれ2行のレコード、3行のレコードが格納されている場合、結合結果は全てのレコードの組合せになり合計 2×3=6 行のレコードになります。
PostgreSQL や MySQL では ON, USING を指定すると INNER JOIN と等価になります。
SELECT * FROM table1 CROSS JOIN table2
は次の SQL 文と等価です。
SELECT * FROM table1, table2
また、PostgreSQL, MySQL では
SELECT * FROM table1 CROSS JOIN table2 USING(id)
は次の SQL 文と等価です。
SELECT * FROM table1 INNER JOIN table2 USING(id)
■ON, USING, NATURALによる結合条件指定
CROSS JOIN をのぞき、JOIN 句には ON, USING, NATURAL のいずれかを指定します。
INNER JOIN … ON 結合条件
INNER JOIN … USING(結合カラム, … )
NATURAL INNER JOIN …
ON:
WHERE 句と同じ書式で結合条件を絞り込む。
書式:
SELECT … FROM table1 JOIN table2 ON 条件式
table1, table2 のidが同じカラム同士を結合したい場合は次のようにします。
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id
AND, OR など複雑な条件で絞り込む事も可能です。
SELECT * FROM table1 AS t1
LEFT JOIN table2 AS t2 ON (
(t1.id = t2.id OR t1.name = t2.name)
AND t1.salary >= t2.age * 1000
)
ON に指定する結合条件には、 WHERE 同様どんな条件式でも指定することが可能ですが、テーブル結合に関係のない条件式はWHERE句に書くようにしましょう。
USING:
カラム名で結合条件を絞り込む。
書式:
SELECT … FROM table1 JOIN table2 USING(カラム1 , カラム2, …)
両方のテーブルに同じカラム名がある場合に有効です。
SELECT * FROM table1 LEFT JOIN table2 USING(id)
は
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id
と同等です。複数指定した場合は全てのカラムの値が一致するレコードを結合します。
NATURAL:
両方のテーブルを比較して名前が同じ全てのカラムを結合条件にして絞り込む。
書式:
SELECT … FROM table1 NATURAL JOIN table2
USING に両テーブルの共通カラムを全て指定したものと同じです。すなわち、
SELECT * FROM table1 NATURAL LEFT JOIN table2
は、両方のテーブルに id, name, age が存在する場合、
SELECT * FROM table1 LEFT JOIN table2 USING (id, name, age)
を指定したのと同等の結果になります。
■複数のJOIN句を組み合わせる
3つ以上のテーブルを複数のJOIN 句で結合する事もできます。
例として、table1, table2. table3 全てのテーブルに同じidが存在するレコードのみ抽出する場合は次のようになります。
SELECT * FROM table1
INNER JOIN table2 USING(id)
INNER JOIN table3 USING(id)
複数のJOIN を組み合わせる時の結合順序は左から順に評価されます。結合順序を制御したい場合、カッコが使用できます。
SELECT * FROM table1 AS t1
LEFT JOIN ( table2 AS t2 INNER JOIN table3 USING(id)) ON t1.value = t2.tag
)
この例では table2, table3の同じidのレコードをINNER JOIN 句を使い結合し、その後 table1 と LEFT JOIN 句で結合されます。
■参考:
– SELECT(PostgreSQL 8.2.5 マニュアル)
– MySQL 5.1 リファレンスマニュアル 12.2.7.1 JOIN 構文(MySQL AB)
– 特殊な結合演算子 SQL(TELCHSCORE by 4DD)
– SQL92