JOIN

2 つ以上のテーブルを関連列で結合する。 INNER / LEFT / RIGHT / FULL / CROSS / SELF / LATERAL を一通り。

INNER JOIN(内部結合)

両側に該当行がある組み合わせのみ返す。最も一般的。

SELECT u.email, p.title
FROM users u
INNER JOIN posts p ON p.user_id = u.id;

-- INNER は省略可
SELECT u.email, p.title
FROM users u JOIN posts p ON p.user_id = u.id;

視覚的なイメージ

users               posts
+----+------+      +----+--------+
| 1  | A    |      | 1  | post1  | user_id=1
| 2  | B    |      | 2  | post2  | user_id=1
| 3  | C    |      | 3  | post3  | user_id=2
+----+------+      | 4  | post4  | user_id=99 ← 親なし
                   +----+--------+

INNER JOIN 結果(user_id=99 は除外、user 3 は除外)
+------+--------+
| A    | post1  |
| A    | post2  |
| B    | post3  |
+------+--------+
      

LEFT OUTER JOIN(左外部結合)

左テーブルの行は必ず残す。右に該当が無ければ NULL。

-- 「全ユーザ + 投稿があれば持ってくる」
SELECT u.email, p.title
FROM users u
LEFT JOIN posts p ON p.user_id = u.id;
LEFT JOIN 結果
+------+--------+
| A    | post1  |
| A    | post2  |
| B    | post3  |
| C    | NULL   |  ← 投稿無しでも残る
+------+--------+
      

「持っていない」検索

-- 「投稿が無いユーザ」
SELECT u.id, u.email
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
WHERE p.id IS NULL;

RIGHT OUTER JOIN(右外部結合)

LEFT の左右逆。実用上はLEFT に書き直すのが定番(読みやすいから)。

FULL OUTER JOIN

両側残す。どちらかに該当が無ければ NULL。

SELECT u.email, p.title
FROM users u
FULL OUTER JOIN posts p ON p.user_id = u.id;

差分検出

-- どちらか片方にしか無い行
SELECT
  COALESCE(a.id, b.id) AS id,
  CASE WHEN a.id IS NULL THEN 'B のみ'
       WHEN b.id IS NULL THEN 'A のみ' END AS where_
FROM table_a a
FULL JOIN table_b b ON a.id = b.id
WHERE a.id IS NULL OR b.id IS NULL;

CROSS JOIN(直積)

条件無しで全組み合わせ。非常に重いので意図して使う。

SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;
-- sizes 3 × colors 5 = 15 行

SELF JOIN(自己結合)

同じテーブルを 2 回参照。階層構造などで使う。

-- comments テーブルが parent_id で階層を持つ
SELECT
  child.id, child.body,
  parent.body AS parent_body
FROM comments child
LEFT JOIN comments parent ON parent.id = child.parent_id;

USING / NATURAL JOIN

-- 同名列で結合
SELECT * FROM users u JOIN posts p USING (user_id);

-- 全同名列で自動結合(事故りやすい、推奨されない)
SELECT * FROM users NATURAL JOIN posts;

3 つ以上のテーブル

SELECT
  u.email,
  p.title,
  c.body AS comment_body
FROM users u
JOIN posts p ON p.user_id = u.id
JOIN comments c ON c.post_id = p.id
WHERE u.email = 'alice@example.com';

ON と WHERE の違い(OUTER JOIN で重要)

-- 「公開中の投稿があれば、ユーザと一緒に」
SELECT u.id, p.title
FROM users u
LEFT JOIN posts p ON p.user_id = u.id AND p.status = 'published';
-- ↑ ON 句に条件 → 投稿無しユーザも残る

-- ↓ WHERE に書くと INNER と同じ結果に
SELECT u.id, p.title
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
WHERE p.status = 'published';

LATERAL JOIN(Postgres)

外側のテーブルの列を内側のサブクエリで使える。各行ごとにサブクエリを評価。

-- 各ユーザの最新 5 投稿
SELECT u.id, u.email, p.*
FROM users u
LEFT JOIN LATERAL (
  SELECT * FROM posts
  WHERE user_id = u.id
  ORDER BY created_at DESC
  LIMIT 5
) p ON true;

Window 関数の ROW_NUMBER() OVER (PARTITION BY ...) でも同じことができる。 LATERAL は SQL 的に直感的、Window は性能が良いケースが多い。

JOIN の典型クエリ

1. ユーザの投稿数

SELECT u.id, u.email, count(p.id) AS post_count
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
GROUP BY u.id;

2. 投稿 + 著者 + コメント数

SELECT
  p.id, p.title,
  u.name AS author,
  count(c.id) AS comment_count
FROM posts p
JOIN users u ON u.id = p.user_id
LEFT JOIN comments c ON c.post_id = p.id
GROUP BY p.id, u.name;

3. ユーザのフォロワー(中間テーブル)

-- follows(follower_id, followee_id)
SELECT followee.email AS being_followed, follower.email AS follower
FROM follows f
JOIN users follower ON follower.id = f.follower_id
JOIN users followee ON followee.id = f.followee_id
WHERE follower.id = 42;

4. 友達 of 友達

SELECT DISTINCT fof.id, fof.email
FROM follows f1
JOIN follows f2 ON f1.followee_id = f2.follower_id
JOIN users fof ON fof.id = f2.followee_id
WHERE f1.follower_id = 42
  AND fof.id != 42;

パフォーマンス

JOIN の代わりに JSON 集約(Postgres)

-- 各投稿に「コメント配列」を埋め込む
SELECT
  p.id, p.title,
  json_agg(json_build_object('id', c.id, 'body', c.body)) AS comments
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
GROUP BY p.id;

REST / GraphQL レスポンスを 1 クエリで作れる強力な技。

失敗パターン

症状原因
結果が増えすぎる1-N JOIN で重複が拡大。集計 / DISTINCT
遅いFK 列にインデックスなし
OUTER JOIN で行が消えるWHERE に条件を書いた → ON 句へ
SELF JOIN で混乱必ず別エイリアス(child / parent)
3 つ以上で迷子CTE で段階分解
早く慣れるコツ

紙に図を描いて、INNER と LEFT で残る行を可視化する。 これに 1 時間費やすと、後の生産性が桁違いに上がる。