この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:T
更新日時:2026-06-11 07:07:02

タイトル: 副問い合わせ (サブクエリ)
SEOタイトル: SQL 副問い合わせ完全ガイド — スカラー / インライン / 相関 / EXISTS / ANY / ALL / CTE

この記事の要点
  • 副問い合わせ (Subquery) = SQL の中に書く別の SELECT。結果を 1 値 / 1 行 / 1 集合として使う
  • 形は 3 種類: スカラー (1 値) / インラインビュー (FROM 句) / 相関 (外側を参照)
  • 述語: IN / NOT IN / EXISTS / NOT EXISTS / ANY / ALL
  • NULL を含む列に NOT IN は禁物 — 1 件でも NULL があると全結果が偽に。NOT EXISTS を使う
  • 深いネストは CTE (WITH) へ書換、性能は JOIN + 適切な Index に置換できることが多い

副問い合わせとは

SQL の SELECT / WHERE / FROM / HAVING の中に書ける、もう一つの SELECT 文のことを 副問い合わせ (Subquery / 子クエリ) と呼びます。外側のクエリを 主問い合わせ (Outer query) と呼びます。

使いどころは大きく 3 つ。

分類返す形典型的な使い場所
スカラー副問い合わせ1 行 1 列 (1 値)SELECT のリスト / WHERE 句の比較
行副問い合わせ1 行 N 列WHERE (a,b) = (SELECT ...)
列副問い合わせN 行 1 列WHERE x IN (SELECT ...) / EXISTS
インラインビュー表 (N 行 N 列)FROM 句に書く一時表

スカラー副問い合わせ

結果が必ず 1 行 1 列に絞れる副問い合わせ。SELECT リストや比較述語の右辺で使えます。

-- 全社員の給与と、会社の平均給与を並べる
SELECT
  emp_id,
  name,
  salary,
  (SELECT AVG(salary) FROM employees) AS avg_salary,
  salary - (SELECT AVG(salary) FROM employees) AS diff
FROM employees;

-- 自部署より給料が高い人だけ
SELECT name, salary
FROM employees e
WHERE salary > (
    SELECT AVG(salary) FROM employees
    WHERE dept_id = e.dept_id        -- ← 相関副問い合わせ
);

注意: 副問い合わせが 2 行以上返してしまうと ORA-01427 / ERROR 1242 等のエラーが出ます。MAX / MIN / LIMIT 1 等で 1 行に絞ります。

列副問い合わせと IN / NOT IN

-- 「東京」部署の社員一覧
SELECT name FROM employees
WHERE dept_id IN (SELECT dept_id FROM departments WHERE city = '東京');

-- 退職済みでないユーザー
SELECT * FROM users
WHERE user_id NOT IN (SELECT user_id FROM retirements);

NOT IN の罠 — 副問い合わせ側に NULL が 1 件でもあると、x NOT IN (1, 2, NULL)「x <> 1 AND x <> 2 AND x <> NULL」 に展開され、最後の比較が UNKNOWN になるため全体が常に偽になります。結果が 0 件 になって悩むのはこれ。

-- NG: retirements.user_id に NULL があると常に 0 件
SELECT * FROM users
WHERE user_id NOT IN (SELECT user_id FROM retirements);

-- OK: NOT EXISTS を使う or NULL を除外
SELECT * FROM users u
WHERE NOT EXISTS (
  SELECT 1 FROM retirements r WHERE r.user_id = u.user_id
);

EXISTS / NOT EXISTS

「存在するか」「存在しないか」だけを問う形。副問い合わせ側の値そのものは見ないので、SELECT 1SELECT * でも結果は同じです。

-- 注文を 1 件以上持っている顧客
SELECT *
FROM customers c
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.customer_id = c.customer_id
);

-- 一度も注文していない顧客
SELECT *
FROM customers c
WHERE NOT EXISTS (
  SELECT 1 FROM orders o
  WHERE o.customer_id = c.customer_id
);

ANY / SOME / ALL

述語意味等価な書き換え
= ANY (...)いずれか一つと等しいIN (...)
<> ALL (...)すべてと等しくないNOT IN (...)
> ANY (...)最小値より大きい> (SELECT MIN(...))
> ALL (...)最大値より大きい> (SELECT MAX(...))
-- 営業部のどの社員よりも給料が高い人
SELECT name, salary FROM employees
WHERE salary > ALL (
  SELECT salary FROM employees WHERE dept_id = 'SALES'
);
-- = SELECT name, salary FROM employees
--   WHERE salary > (SELECT MAX(salary) FROM employees WHERE dept_id = 'SALES');

インラインビュー (FROM 句の副問い合わせ)

-- 部署ごとの平均給与を出して、その中で平均が 50 万以上の部署のみ
SELECT d.dept_name, t.avg_sal
FROM departments d
JOIN (
  SELECT dept_id, AVG(salary) AS avg_sal
  FROM employees
  GROUP BY dept_id
) t ON t.dept_id = d.dept_id
WHERE t.avg_sal >= 500000;

相関副問い合わせ

副問い合わせの中で外側の列を参照する形。外側 1 行ごとに副問い合わせが評価されるため、大量データでは遅くなりがち。可能なら JOIN + GROUP BY に書き換えると速くなります。

-- 部署ごとに最も給料の高い人 (相関版)
SELECT name, dept_id, salary
FROM employees e1
WHERE salary = (
  SELECT MAX(salary) FROM employees e2
  WHERE e2.dept_id = e1.dept_id    -- ← 外側参照
);

-- 同じ結果を JOIN で
SELECT e.name, e.dept_id, e.salary
FROM employees e
JOIN (
  SELECT dept_id, MAX(salary) AS max_sal
  FROM employees GROUP BY dept_id
) m ON m.dept_id = e.dept_id AND m.max_sal = e.salary;

CTE (WITH) との比較

ネストが深くなると読みづらく、デバッグも面倒です。WITH 句 (Common Table Expression) を使うと、副問い合わせを上から順に名前付きで定義できます。Oracle / PostgreSQL / SQL Server / MySQL 8.0+ で利用可能。

WITH dept_avg AS (
  SELECT dept_id, AVG(salary) AS avg_sal
  FROM employees
  GROUP BY dept_id
),
high_dept AS (
  SELECT dept_id FROM dept_avg WHERE avg_sal >= 500000
)
SELECT e.name, e.dept_id, e.salary
FROM employees e
JOIN high_dept h ON h.dept_id = e.dept_id;

性能 — JOIN への置換と Index

パターン性能上の注意
列副問い合わせ x IN (SELECT ...)多くの DB で内部的に半結合 (semi-join) に最適化される。Index が効くなら速い
相関 EXISTS外側 1 行ごとに 1 件見つかれば打ち切り。一般に IN より高速
NOT IN (NULL 含む)結果 0 件のバグ + 性能も劣化 — NOT EXISTS に書換
スカラー副問い合わせを SELECT に書く外側 1 行ごとに評価され遅い — JOIN + GROUP BY が高速なケースが多い
FROM 句の副問い合わせマテリアライズされる DB と、外側にプッシュダウンされる DB がある — EXPLAIN で確認

ネストの深さ制限

DBネスト上限
OracleFROM 句で 255 階層、WHERE 内は実質無制限だが推奨は数階層まで
SQL Server32 階層
MySQL明示上限なし。ただし 8.0 未満は派生テーブルの最適化が弱い
PostgreSQL明示上限なし

FAQ

Q: 副問い合わせと JOIN、どちらが速いですか?
A: 最近のオプティマイザは両者を同じ実行計画に変換することが多く、差は出にくいです。読みやすさ優先で書き、遅かったら EXPLAIN を見て JOIN や CTE に書き換えるのが定石。

Q: NOT IN と NOT EXISTS の使い分けは?
A: 「副問い合わせ側に NULL が混入し得るなら NOT EXISTS」が安全策。NOT IN は NULL が無いと保証できる列だけに使ってください。

Q: 副問い合わせは何階層までネストして大丈夫?
A: 機能上は深くできますが、可読性とオプティマイザの安定性のため 3 階層を超えたら WITH 句に分解 するのがおすすめです。