SQL JOIN — 複数テーブル結合パターンの応用

応用JOIN応用結合パターンWeb開発PostgreSQL対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 1

SELF JOIN — 同一テーブルを2つの役割で結合し、階層関係を表現する

SELF JOIN自己結合組織階層エイリアス必須
前提知識

SELF JOIN(自己結合)は、同じテーブルを2つの異なるエイリアスで参照し、行同士を結合するテクニックです。
「社員と上司が同じ employees テーブルに存在し、manager_id が同テーブルの emp_id を参照する」組織階層は、その代表例です。

FROM   employees AS e   -- 「部下」側として参照
LEFT JOIN employees AS m  -- 「上司」側として参照
  ON e.manager_id = m.emp_id;
エイリアスが命: SELF JOINでは AS e(部下)と AS m(上司)のように必ず異なるエイリアスを付ける必要があります。エイリアスがないとDBはどちらのテーブルを指しているか判断できません。
問題

社員テーブル(employees)には、各社員の manager_id(直属上司の emp_id)が格納されています。

このテーブルを自己結合し、各社員名(employee)と、その直属上司の名前(manager)を一覧で取得してください。上司がいない社員(社長)も出力に含めてください。

使用テーブル
▸ employees
emp_idnamemanager_id
1田中NULL
2佐藤1
3山田1
4鈴木2
期待出力
employeemanager
田中NULL
佐藤田中
山田田中
鈴木佐藤
QUESTION 2

アンチJOIN — 「一度も購入していないユーザー」をLEFT JOIN + IS NULLで抽出する

LEFT JOINIS NULLアンチJOIN差集合
前提知識

「Aテーブルには存在するが、Bテーブルには存在しないレコード」を取得するパターンをアンチJOIN(Anti-JOIN)と呼びます。
LEFT JOIN で全件を残し、右テーブル側が NULL になった行だけをWHEREで絞り込む2段構えの手法です。

FROM   users AS u
LEFT JOIN purchases AS p ON u.user_id = p.user_id
WHERE  p.user_id IS NULL;   -- 右テーブルにマッチがない行だけを残す
NOT IN との使い分け: WHERE u.user_id NOT IN (SELECT user_id FROM purchases) でも同じ結果を得られますが、NOT IN は右リストに NULL が混入すると結果が空になるリスクがあります。アンチJOINパターンの方が安全で高速なため、実務では推奨されています。
問題

ユーザー一覧(users)と購入履歴(purchases)のテーブルがあります。

一度も購入をしたことがないユーザーの名前だけを取得してください。

使用テーブル
▸ users
user_idname
1田中
2佐藤
3山田
▸ purchases
purchase_iduser_iditem
11商品A
21商品B
32商品C
期待出力
name
山田
QUESTION 3

集計JOIN — サブクエリで部門平均を先に計算し、個人給与と比較する

INNER JOINGROUP BYサブクエリ集計との比較
前提知識

「各社員の給与が、自部門の平均より高いか低いか」を判定するには、一度集計してから個人データと結合するというアプローチが必要です。
サブクエリ(または後述のCTE)で GROUP BY 集計を先に行い、その結果テーブルをJOINすることで、個人行を残したまま集計値を横付けできます。

FROM employees AS e
INNER JOIN (
  SELECT dept_id, AVG(salary) AS avg_salary
  FROM   employees
  GROUP BY dept_id
) AS da ON e.dept_id = da.dept_id;
なぜ WHERE に AVG を書けないか: WHERE salary > AVG(salary) は構文として無効です。WHERE は集計前の各行を評価するため、集計関数を使えません。「先に集計 → 結果テーブルをJOIN」が正しい設計です。
問題

社員テーブル(employees)に給与(salary)と部門ID(dept_id)が格納されています。

各社員の名前・給与・所属部門の平均給与(avg_salary)を一覧で取得してください。

使用テーブル
▸ employees
emp_idnamedept_idsalary
1田中10500
2佐藤10700
3山田20600
4鈴木20400
期待出力
namesalaryavg_salary
田中500600
佐藤700600
山田600500
鈴木400500
QUESTION 4

3テーブル連続LEFT JOIN — ECサイトの注文・ユーザー・商品を一気に結合する

LEFT JOIN x2連続結合EC設計NULL保持
前提知識

実務のWebアプリケーションでは、1つのクエリで3つ以上のテーブルを結合することが頻繁に発生します。
LEFT JOIN を連続して重ねる場合、常に直前までの結合結果が「左テーブル」になります。右側のテーブルにマッチがなくても行を落とさないことが、NULL保持のポイントです。

FROM         orders      AS o
LEFT JOIN    users       AS u  ON o.user_id    = u.user_id
LEFT JOIN    order_items AS oi ON o.order_id   = oi.order_id;
JOIN の順序と結果の関係: LEFT JOIN を連続する場合、FROM に指定したテーブルが「最も保護される側」です。どの後続テーブルにマッチしなくても、元の行は必ず結果に残ります。
問題

注文テーブル(orders)、ユーザーテーブル(users)、注文明細テーブル(order_items)の3つがあります。

3テーブルを結合して、注文ID・ユーザー名・商品名・数量を一覧で取得してください。商品が登録されていない注文(order_id=3)も出力に含めること

使用テーブル
▸ orders
order_iduser_id
11
22
33
▸ users
user_idname
1田中
2佐藤
3山田
▸ order_items
item_idorder_idproduct_nameqty
11Tシャツ2
21ジーンズ1
32スニーカー1
期待出力
order_idnameproduct_nameqty
1田中Tシャツ2
1田中ジーンズ1
2佐藤スニーカー1
3山田NULLNULL
QUESTION 5

CTE + JOIN — WITH句で事前集計し、ユーザー情報と結合する現代的SQLパターン

WITH句(CTE)LEFT JOINCOALESCE可読性向上
前提知識

CTE(Common Table Expression / 共通テーブル式)は、WITH 名前 AS (サブクエリ) の形でSQLの冒頭に「名前付きの仮想テーブル」を定義する構文です。
複雑なサブクエリをネストさせる代わりに、上から下へ読める構造で書けるためコードの可読性が飛躍的に向上します。

WITH cte_name AS (
  SELECT ...  -- 事前集計や整形処理
)
SELECT ...
FROM   main_table
LEFT JOIN cte_name ON ...;
モダンSQLの推奨スタイル: PostgreSQL・MySQL 8.0・BigQuery・Snowflake など主要なRDBすべてでCTEが使えます。サブクエリ結合と同じ結果を、より読みやすく書けるのがCTEの最大の利点です。
問題

ユーザーテーブル(users)と注文テーブル(orders)があります。

WITH句を使ってユーザーごとの注文件数(order_count)と合計金額(total_amount)を集計し、ユーザー情報と結合して出力してください。
一度も注文していないユーザー(山田)も出力し、注文件数は 0、合計金額は NULL で表示すること

使用テーブル
▸ users
user_idname
1田中
2佐藤
3山田
▸ orders
order_iduser_idamount
113000
215000
322000
期待出力
nameorder_counttotal_amount
田中28000
佐藤12000
山田0NULL