サブクエリとCTE — クエリを組み合わせる
SQLのサブクエリ(副問い合わせ)とCTE(WITH句)の使い方を解説します。スカラサブクエリ・相関サブクエリ・IN/EXISTS・派生テーブルの違いを基礎から整理します。
「平均より給料が高い社員」「一度も注文していない顧客」のように、別の問い合わせの結果を使って絞り込む場面があります。このとき使うのがサブクエリ(副問い合わせ)です。クエリの中に別のクエリを埋め込むことで、段階的な条件を1文で表現できます。
スカラサブクエリ — 1つの値を返す
もっとも分かりやすいのが、1つの値(スカラ)を返すサブクエリです。たとえば「全体の平均給料より高い社員」を求めるには、まず平均をサブクエリで計算し、それと比較します。
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);括弧の中の SELECT AVG(salary) FROM employees が先に実行され、その1つの値(平均給料)と各行の salary を比較します。
IN — 複数の値のいずれかに一致
サブクエリが複数の値を返す場合は IN を使います。「注文をしたことがある顧客」を求める例です。
SELECT name
FROM customers
WHERE id IN (SELECT customer_id FROM orders);サブクエリ SELECT customer_id FROM orders が「注文した顧客IDの一覧」を返し、その中に id が含まれる顧客だけが残ります。
逆に「一度も注文していない顧客」は NOT IN で書けます。
SELECT name
FROM customers
WHERE id NOT IN (SELECT customer_id FROM orders);ただし NOT IN はサブクエリ側に NULL が含まれると意図しない結果になることがあります。その場合は次の NOT EXISTS の方が安全です。
EXISTS — 行が存在するか
EXISTS は「条件を満たす行が1つでもあるか」を判定します。外側の行ごとにサブクエリを評価する相関サブクエリとよく組み合わせます。
SELECT name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);サブクエリの中で外側のテーブル c を参照している点がポイントです。これは「その顧客の注文が1件も存在しない」=「一度も注文していない顧客」を意味します。NOT IN より NULL に強く、実務でよく使われます。
派生テーブル — FROM句のサブクエリ
サブクエリは FROM 句にも書けます。これを派生テーブルと呼び、集計した結果をさらに処理したいときに便利です。
SELECT dept, avg_salary
FROM (
SELECT dept, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept
) AS dept_avg
WHERE avg_salary >= 550;内側で部門ごとの平均を出し、それを1つのテーブルのように扱って外側で絞り込んでいます。
CTE(WITH句)— 名前を付けて読みやすく
サブクエリが増えると、入れ子が深くなって読みにくくなります。そこで役立つのが CTE(Common Table Expression、共通テーブル式)です。WITH で先に名前を付け、あとから普通のテーブルのように使えます。
WITH dept_avg AS (
SELECT dept, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept
)
SELECT dept, avg_salary
FROM dept_avg
WHERE avg_salary >= 550;先ほどの派生テーブルと同じ結果ですが、処理の流れが上から下へ読めるようになり、意図が伝わりやすくなります。複数の CTE をカンマで並べて、段階的に組み立てることもできます。
サブクエリと JOIN の使い分け
サブクエリで書けることの多くは JOIN でも書けます。目安として、
- 存在チェック・絞り込み(〜に含まれる/含まれない)はサブクエリ(
IN/EXISTS)が読みやすい - 複数テーブルの列を並べて出すなら
JOINが素直
迷ったら、まず動く形で書き、あとから読みやすい方に整えるとよいでしょう。
まとめ
- スカラサブクエリは1つの値を返して比較に使う
IN/NOT INは値の集合との一致、EXISTS/NOT EXISTSは存在判定FROM句のサブクエリ(派生テーブル)で集計結果をさらに処理できる- 入れ子が深くなったら CTE(
WITH)で名前を付けて整理する
サブクエリと CTE を使いこなせると、複雑な問い合わせも段階的に組み立てられます。「サブクエリ」カテゴリの演習問題で、実際に手を動かして確認してみてください。