概要(サブクエリ=「SQL の中にもう一個 SQL を入れる」)
サブクエリは、
「SQL 文の中に、さらに別の SQL 文(小さな SELECT)を入れるテクニック」です。
イメージとしては、
一回クエリを実行して“中間結果”を作る
その中間結果を、もう一回別のクエリから使う
これを「一発の SQL の中でやる」のがサブクエリです。
最初は少し難しく見えますが、
「サブクエリは、ただの SELECT の“入れ子”」
という感覚を持てると、一気に怖くなくなります。
ここでは、よくあるパターンに絞って、
例題を使いながら丁寧にかみ砕いていきます。
サブクエリの基本イメージを固める
まずは「普通の SELECT」を二段階で考えてみる
例えば、次の users テーブルがあるとします。
users テーブル
id | name | age
---+--------+----
1 | Taro | 25
2 | Hanako | 30
3 | Ken | 19
「一番年齢が高いユーザーを知りたい」とします。
二段階で考えると、こうなります。
1 回目のクエリで「最大の年齢」を求める:
SELECT MAX(age) FROM users;
SQL結果が 30 だとします。
2 回目のクエリで「age = 30 のユーザー」を取る:
SELECT *
FROM users
WHERE age = 30;
SQLこれを「1 本の SQL にまとめたもの」がサブクエリです。
同じことをサブクエリで書く
さっきの二段階を、サブクエリで 1 本にするとこうなります。
SELECT *
FROM users
WHERE age = (
SELECT MAX(age)
FROM users
);
SQLWHERE の右側に、もう一つ SELECT が入っています。
このカッコの中の SELECT が「サブクエリ」です。
流れとしては、
内側の SELECT で MAX(age) を計算する
その結果(30)を使って、外側の WHERE age = 30 を評価する
ということを、データベースが一気にやってくれます。
「一回 SELECT して、その結果をもう一回使う」
これを“手でやるか”“SQL にやらせるか”の違いだと捉えると分かりやすいです。
WHERE 句で使うサブクエリ(「条件の中に SELECT」)
例1:最大値・最小値と一致する行を取る
先ほどの「最年長ユーザー」を取る例は、
WHERE 句の中でサブクエリを使う典型パターンです。
SELECT *
FROM users
WHERE age = (
SELECT MAX(age)
FROM users
);
SQLここで重要なのは、
サブクエリが「1 行 1 列だけ返す」形になっていることです。
MAX(age) は 1 つの値だけなので、age = (その値) という比較ができます。
同じように、「最年少ユーザー」なら MIN(age) を使います。
SELECT *
FROM users
WHERE age = (
SELECT MIN(age)
FROM users
);
SQL例2:IN と組み合わせて「複数行の結果」を条件に使う
今度は、orders テーブルを追加します。
orders テーブル
id | user_id | amount
---+---------+-------
1 | 1 | 5000
2 | 1 | 3000
3 | 2 | 8000
「注文を一度でもしたことがあるユーザーだけを users から取りたい」とします。
まず、「注文をしたことがある user_id の一覧」を考えます。
SELECT DISTINCT user_id
FROM orders;
SQLこれをサブクエリとして使い、
users 側で「id がその中に含まれているユーザー」を取ります。
SELECT *
FROM users
WHERE id IN (
SELECT DISTINCT user_id
FROM orders
);
SQLIN の中にサブクエリを入れることで、
サブクエリが返した複数の user_id のどれかに一致する行だけを残す
という条件を書けます。
このパターンは、
「別テーブルに存在するものだけ」
「別テーブルに紐づきがあるものだけ」
を絞り込みたいときによく使われます。
FROM 句で使うサブクエリ(「一時的なテーブルとして使う」)
サブクエリに名前を付けて「仮想テーブル」にする
サブクエリは、FROM の中にも書けます。
この場合、サブクエリの結果は「一時的なテーブル」として扱われます。
例えば、study_logs テーブルがあるとします。
study_logs テーブル
id | user_name | subject | minutes
---+-----------+---------+--------
1 | Taro | math | 60
2 | Taro | english | 30
3 | Hanako | math | 45
4 | Taro | math | 40
5 | Hanako | english | 50
「ユーザーごとの合計勉強時間を出し、その結果をさらに“多い順に並べたい”」とします。
まず、ユーザーごとの合計時間を出すクエリはこうです。
SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name;
SQLこれを「一時テーブル」として FROM に入れ、
外側で ORDER BY する書き方ができます。
SELECT *
FROM (
SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name
) AS user_totals
ORDER BY total_minutes DESC;
SQLここで、
カッコ内の SELECT がサブクエリAS user_totals が、その結果に付けた「テーブル名」
です。
外側からは、user_totals というテーブルがあるかのように扱えます。
なぜわざわざ FROM サブクエリを使うのか
この例は、実はサブクエリを使わなくても書けます。
SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name
ORDER BY total_minutes DESC;
SQLただ、もう少し複雑なケースでは、
一度集計した結果に対して、さらに別の集計や JOIN をしたい
ということがよくあります。
そのときに、
「一回集計した結果を“テーブル化”して、もう一度 SELECT する」
という発想ができると、
SQL の表現力が一気に広がります。
FROM サブクエリは、
「中間結果に名前を付けて、もう一度使う」ための道具だと捉えると良いです。
SELECT 句で使うサブクエリ(「列の値として小さな SELECT」)
1 行ごとに「別のテーブルから値を引いてくる」イメージ
SELECT の列の中にもサブクエリを書くことができます。
これは少し高度ですが、イメージだけ掴んでおくと役に立ちます。
例えば、users と orders があって、
「ユーザーごとの合計購入金額を、1 行で見たい」とします。
users
id | name
---+--------
1 | Taro
2 | Hanako
orders
id | user_id | amount
---+---------+-------
1 | 1 | 5000
2 | 1 | 3000
3 | 2 | 8000
JOIN + GROUP BY で書くならこうです。
SELECT users.id,
users.name,
SUM(orders.amount) AS total_amount
FROM users
LEFT JOIN orders
ON users.id = orders.user_id
GROUP BY users.id, users.name;
SQLこれを「SELECT サブクエリ」で書くと、こうも書けます。
SELECT
u.id,
u.name,
(
SELECT SUM(amount)
FROM orders o
WHERE o.user_id = u.id
) AS total_amount
FROM users u;
SQLここで起きていることは、
外側の SELECT で users の 1 行を取る
その 1 行ごとに、内側のサブクエリが実行される
サブクエリは「そのユーザーの注文の合計金額」を返す
それを total_amount 列として表示する
という流れです。
JOIN で書く方が一般的ですが、
「1 行ごとに別テーブルから値を引いてくる」
という発想が必要なときに、SELECT サブクエリが使われます。
サブクエリを理解するための“考え方のコツ”
いきなり 1 本で書かず、「まず 2 本に分けて考える」
サブクエリでつまずく一番の原因は、
いきなり 1 本の SQL で書こうとすることです。
例えば、
「最年長ユーザーを取りたい」
「合計時間が 100 分以上のユーザーだけ取りたい」
「注文をしたことがあるユーザーだけ取りたい」
こういう要件が出てきたとき、
まずは「2 本のクエリ」に分解して考えてください。
例:「合計時間が 100 分以上のユーザーだけ」
1 本目:ユーザーごとの合計時間を出す
SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name;
SQL2 本目:「total_minutes >= 100 のユーザーだけ」を絞る
これをサブクエリにすると、
SELECT *
FROM (
SELECT user_name, SUM(minutes) AS total_minutes
FROM study_logs
GROUP BY user_name
) AS t
WHERE total_minutes >= 100;
SQLという形になります。
「まず素直に 2 本で考える → それを 1 本に畳む」
このステップを踏むと、サブクエリの意味が自然に見えてきます。
「サブクエリは、ただの SELECT」として読んでみる
サブクエリを読むときは、
カッコの中だけを取り出して「普通の SELECT」として眺めてください。
例えば、
SELECT *
FROM users
WHERE id IN (
SELECT DISTINCT user_id
FROM orders
);
SQLこのとき、
内側の SELECT は「注文したことがある user_id の一覧」
外側の WHERE id IN (…) は「その一覧に含まれるユーザーだけ」
というふうに、
内側と外側を別々に理解してから、
「外側が内側の結果をどう使っているか」を見ると、
頭の中が整理されやすくなります。
まとめ(サブクエリは「SQL の中に小さな SQL を入れて、考え方をそのままコードにする」)
サブクエリを初心者目線で整理すると、こうなります。
サブクエリは「SQL の中に入れ子になった SELECT」で、
「一回 SELECT して得た中間結果を、もう一回別の SELECT から使う」ための仕組み。
WHERE 句の中で使えば、「最大値と一致する行」「別テーブルに存在するものだけ」などの条件を、
FROM 句の中で使えば、「一度集計した結果を仮想テーブルとして再利用」できる。
SELECT 句の中で使えば、「1 行ごとに別テーブルから値を引いてくる」ようなことも書ける。
考えるときは、
まず「2 本のクエリに分けて」日本語で整理し、
それを「内側の SELECT(サブクエリ)」と「外側の SELECT」に畳む、
というステップを踏むと理解しやすいです。
