こんにちは、かつコーチです。
「一度取得した結果を、もう一度別の条件で絞り込みたい」「他のテーブルの集計結果と比較したい」——そんな場面でJOINだけでは書きづらいと感じたことはありませんか。
この記事では、そうした悩みを解決するサブクエリの基本を、実際のコード例を交えて解説します。
読み終わる頃には、「クエリの中にクエリを書く」という発想に慣れ、自分のSQLの表現力を一段階広げられるようになります。
サブクエリとは?
クエリの中に埋め込む別のクエリ
サブクエリ(副問い合わせ)とは、SELECT文などのSQL文の中に、丸かっこで囲んで埋め込む別のSELECT文のことです。
外側のクエリをメインクエリ、内側に埋め込まれたクエリをサブクエリと呼びます。
サブクエリはまず単独で実行され、その結果が値やリストとしてメインクエリに渡される、という流れで処理されます。
なぜサブクエリが必要なのか
以前の記事「3つ以上のテーブルを結合する」で解説したJOINは、複数テーブルを横に並べて結合する仕組みでした。
一方サブクエリは、「ある集計結果」や「ある条件を満たす値のリスト」を、先に計算してからメインクエリの条件として使いたいときに向いています。
たとえば「平均購入額より多く使っている顧客だけを表示したい」というような、「まず基準値を計算してから、それを条件に使う」というロジックは、JOINよりもサブクエリの方が自然に書けます。
基本の書き方
この記事では、次のテーブルを使います。
CREATE TABLE customers (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount INT NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
INSERT INTO customers (name) VALUES ('田中'), ('佐藤'), ('鈴木');
INSERT INTO orders (customer_id, order_date, total_amount) VALUES
(1, '2026-08-01', 3000),
(1, '2026-08-10', 9000),
(2, '2026-08-03', 2000),
(3, '2026-08-05', 7000);
手順1:WHERE句で使うサブクエリ(単一値を返す)
「全注文の平均金額より高い注文だけを表示したい」というケースです。
SELECT *
FROM orders
WHERE total_amount > (SELECT AVG(total_amount) FROM orders);
丸かっこの中のSELECT AVG(total_amount) FROM ordersが先に実行され、平均額(この例では5250)という単一の値に置き換わります。
その結果、メインクエリはWHERE total_amount > 5250と同じ意味になります。
手順2:WHERE句で使うサブクエリ(複数値のリストを返す)
「東京都に住む顧客の注文だけを表示したい」というように、サブクエリが複数の値を返す場合はINと組み合わせます。
SELECT *
FROM orders
WHERE customer_id IN (
SELECT id FROM customers WHERE name IN ('田中', '鈴木')
);
内側のサブクエリが(1, 3)という顧客IDのリストを返し、外側のクエリはそのリストに含まれるcustomer_idの注文だけを取得します。
以前の記事「IN・BETWEENで複数条件・範囲を指定する」で扱ったINは、こうしてサブクエリの結果を受け取る先としても使えます。
手順3:FROM句で使うサブクエリ
サブクエリはFROM句の中で「一時的なテーブル」として使うこともできます。
SELECT customer_id, total_spent
FROM (
SELECT customer_id, SUM(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
) AS customer_totals
WHERE total_spent >= 5000;
内側のサブクエリで「顧客ごとの合計購入額」を先に集計し、それをcustomer_totalsという名前の一時テーブルとして扱い、外側でさらに絞り込んでいます。
FROM句でサブクエリを使う場合は、必ずASでエイリアス(別名)を付ける必要がある点に注意してください。
つまずきやすい設定・注意点
サブクエリが複数行返るのにIN以外を使ってエラーになる
❌ Before:複数行を返すサブクエリを、単一値専用の比較演算子(=など)で受けてしまう
SELECT *
FROM orders
WHERE customer_id = (
SELECT id FROM customers WHERE name IN ('田中', '鈴木')
);
このクエリはMySQLで実行するとERROR 1242: Subquery returns more than 1 rowというエラーになります。
=は「サブクエリの結果が1行だけ」という前提の演算子だからです。
✅ After:複数行を返す可能性があるサブクエリにはINを使う
SELECT *
FROM orders
WHERE customer_id IN (
SELECT id FROM customers WHERE name IN ('田中', '鈴木')
);
私が実際にこのエラーに遭遇したのは、「絞り込み条件を1件だと思い込んでいたら、実は同名の顧客が複数いた」というケースでした。
「サブクエリの結果は複数行になる可能性がないか」を常に意識しておくと、このエラーを未然に防げます。
サブクエリとJOINの使い分け
サブクエリで書ける処理は、JOINで書き換えられることも多くあります。
どちらを選ぶべきか迷ったときの判断軸を整理しておきます。
| 観点 | サブクエリが向く場面 | JOINが向く場面 |
|---|---|---|
| 目的 | 「基準値」や「条件リスト」を先に計算したいとき | 複数テーブルの列を横に並べて一覧表示したいとき |
| 可読性 | 段階的なロジックが読みやすい | 結合条件が明確で見通しやすい |
| パフォーマンス | 大量データではJOINより遅くなる場合がある | 一般的にJOINの方が最適化されやすい |
「表示したい列が複数テーブルにまたがるならJOIN」「条件の中で別の集計結果を使いたいならサブクエリ」というのが、判断の出発点になります。
応用・一歩先の使い方
EXISTS句との組み合わせ
サブクエリの結果が「存在するかどうか」だけを判定したい場合は、INの代わりにEXISTSを使う方法もあります。
SELECT *
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);
EXISTSはサブクエリの中身そのものではなく、「1行でも該当するか」だけを見る点がINとの違いです。
大量データを扱う場合、EXISTSの方が効率よく動作するケースもあります。
相関サブクエリへの接続
ここまで紹介したサブクエリは、外側のクエリと関係なく単独で実行できる「独立したサブクエリ」でした。
一方、上のEXISTSの例のように、サブクエリの中で外側のクエリの列(c.id)を参照する書き方は、相関サブクエリという一段レベルの高い書き方につながります。
次回の記事「相関サブクエリの仕組みと使いどころ」で、この違いを詳しく掘り下げます。
まとめ
この記事のポイント
- サブクエリはSQL文の中に埋め込む別のSELECT文で、先に実行されてから結果がメインクエリに渡される
- WHERE句で単一値を返すサブクエリは比較演算子と、複数値を返すサブクエリはINと組み合わせる
- FROM句でもサブクエリを使え、その場合は必ずエイリアスを付ける
- 複数行を返すサブクエリを
=で受けるとエラーになるので、INかEXISTSを使う
次に読むべき記事
サブクエリの発展形である相関サブクエリを学びたい方は、次回の「相関サブクエリの仕組みと使いどころ」に進んでください。
タグ: SQL, 中級者向け, サブクエリ
