サブクエリとビュー
概要
- 日程: Day 2 / セッション 7
- 時間: 13:30-14:00
- 形式: 座学
- ゴール: スカラー/インライン/EXISTS型サブクエリの違いを例示でき、ビューの目的を1文で説明できる
- 学習形式: 対話型解説
導入(5分)
ここまでで皆さんは、SELECT、WHERE、JOIN、GROUP BY と「SQLの主要な部品」をひと通り触ってきました。ここで一つ問いを投げます。「平均給与より高い社員だけを取り出す」SQLを、今すぐ書けますか?
普通に書こうとすると、まず SELECT AVG(salary) FROM employees; で平均値を出して、その数字をメモして、もう一度 SELECT * FROM employees WHERE salary > 42000; のように書く——という2段階になります。これ、ちょっと面倒ですよね。しかも給与データが変わるたびに、毎回メモを取り直す必要があります。
このセッションで学ぶ「サブクエリ」と「ビュー」は、この「2段階を1つにまとめる」「同じクエリを名前で呼び出す」ための仕組みです。
本編(20分)
1. サブクエリ=クエリの中のクエリ
サブクエリとは、その名のとおり「SQL文の中に書かれる、もう一つのSQL文」です。これは、いわば料理レシピの中の「ソースは別で作っておく」というカッコ書きのようなものです。
サブクエリの形は使われる場所で3種類に分かれます。
| 種類 | 何を返す? | 主な使いどころ |
|---|---|---|
| スカラーサブクエリ | 値1つ(1行1列) | WHERE で値と比較する |
| インラインビュー(派生表) | 表まるごと | FROM の中で一時的なテーブルとして使う |
| EXISTSサブクエリ | 「存在する/しない」の真偽 | WHERE で「〜がある行だけ」絞り込む |
コード例・実例
スカラーサブクエリ:平均給与より高い社員
SELECT first_name, last_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
カッコの中が「平均給与」を返し、外側のWHEREがそれを使って比較しています。
インラインビュー:部署別の平均給与をさらに並べ替える
SELECT dept_avg.department_id, dept_avg.avg_salary
FROM (
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
) AS dept_avg
ORDER BY dept_avg.avg_salary DESC;
カッコの中身が「一時テーブル dept_avg」になり、外側の SELECT で並び替えています。
EXISTSサブクエリ:注文がある顧客だけ
SELECT customer_name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);
EXISTS は「中のSELECTが1行でも返したらtrue」を意味します。中の SELECT 1 の 1 は何でもよく、慣習的に1を書きます。
ここがポイント
- スカラーは「値」、インラインは「表」、EXISTSは「真偽」を返すと覚える
- スカラーサブクエリは必ず1行1列を返すSQLでないとエラーになる
- JOINで書けることはJOINで書く方が読みやすい場合が多い。サブクエリは「JOINだと書きにくい」場面で使う
コラム
サブクエリは「カッコの中身を先に頭で実行する」と読みやすくなりますが、データベース内部の話をすると、賢いオプティマイザは必ずしもその順番で実行しているわけではありません。「平均給与より高い社員を出して、と書いたら、DBはまず全社員と平均給与を結合してから比較していた」みたいなことが普通に起こります。料理人に「最初に野菜を切って」とお願いしたら、こっそり同時並行で湯も沸かしていた、くらいのイメージです。仕上がりさえ正しければ細かい手順は任せて大丈夫、というのがSQLの面白いところです。
2. ビュー=名前付き保存クエリ
ビュー(VIEW)は、「よく使うSELECT文に名前を付けて、テーブルのように扱えるようにする」仕組みです。これは、いわばスマホの「よく使う設定」ショートカットのようなものです。毎回深い階層まで潜らなくても、ホーム画面のショートカット1つで開けるようになります。
コード例・実例
部署別の平均給与をビューにしてみます。
CREATE VIEW v_dept_avg_salary AS
SELECT
d.department_name,
AVG(e.salary) AS avg_salary,
COUNT(*) AS member_count
FROM employees e
JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_name;
これだけ作っておけば、後は普通のテーブルのように呼び出せます。
SELECT * FROM v_dept_avg_salary ORDER BY avg_salary DESC;
ここがポイント
- ビューは「データの本体」を持たない。実体は元のテーブル。呼び出した瞬間に裏のSELECTが走る
- 命名は
v_で始めるなど「ビューだと分かる接頭辞」を付けると親切 - 不要になったビューは
DROP VIEW v_dept_avg_salary;で削除できる
コラム
ビューの本当の威力は、「複雑さを隠してくれる」ことにあります。たとえば「アクティブな顧客(直近3ヶ月で注文している顧客)」の定義が、社内で何度も変わったとします。普通のSELECTを書いていると、定義変更のたびに全プログラムを直す羽目になります。一方ビューにしておけば、v_active_customers の中身を1か所書き換えるだけで全社が新しい定義に追従できます。「定義を1か所にまとめて、皆で同じ言葉を使う」。ビューはSQLの中の「公式辞書」と思ってください。
3. サブクエリとビュー、どう使い分ける?
ざっくり次のように考えると迷いません。
- 1回しか使わない複雑な絞り込み:サブクエリで書く
- 何度も同じ集計を呼び出す:ビューにする
- 複雑なJOINを後ろの人にも見せたい:ビューにしてSQLを短く保つ
💬 AIに聞いてみよう
- 「
EXISTSとINはどちらも『存在する』を表現できるけど、何が違うの?」 - 「ビューと『一時テーブル(TEMP TABLE)』はどう違うの?」
- 「サブクエリは何重まで入れていい?読みやすさの限界はどこ?」
まとめ(5分)
サブクエリは「クエリの中のクエリ」、ビューは「名前を付けて保存したSELECT」。サブクエリには値を返すスカラー型、表を返すインライン型、真偽を返すEXISTS型の3種があります。ビューは複雑さを隠し、定義を1か所にまとめる役割を果たします。
次のセッションでは、実際にサブクエリを書いてビューを作る演習に入ります。「平均給与より高い社員」をすぐSQLにできるか、思い出しながら手を動かしてみてください。
🔄 振り返りチェック
- スカラー/インライン/EXISTS の3つを、それぞれ「何を返すか」で説明できますか?
- ビューを使うメリットを1文で言えますか?
- 「サブクエリとビュー、どっちにする?」の判断基準を一つ挙げられますか?
補足資料
- 参考リンク: PostgreSQL公式ドキュメント - サブクエリ / CREATE VIEW
- 発展課題: 「直近1ヶ月で注文がある顧客」を返すビューを設計してみる。
v_recent_customersのような名前で、必要な列を考えてみよう
学習ガイド
想定される質問と回答例
| 質問 | ヒント |
|---|---|
| サブクエリは遅いって本当? | 多くのケースでJOINに自動変換される。明らかに遅いのは「相関サブクエリ(外側の行ごとに内側が走る)」を素朴に書いた場合 |
| ビューを更新するとどうなる?元のテーブルが変わる? | 通常のビューは「読み取り専用」と思ってよい。条件を満たせばINSERT/UPDATE可能だが、最初は読み取り目的で十分 |
| マテリアライズドビューって何? | 結果を実体としてキャッシュするビュー。普通のビューは呼ぶたびに計算、マテリアライズドは事前計算済み。重い集計の高速化に使う |
EXISTS の中の SELECT 1 の1って何? |
「何でもいい」のサイン。EXISTSは行があるかだけ見るので、列の中身は無関係。SELECT * でもOK |
つまずきやすいポイント
| つまずきポイント | ヒント |
|---|---|
| スカラーサブクエリが複数行返してエラー | 集約関数(AVG/MAX等)か、LIMIT 1 で1行に絞る |
| インラインビューに名前(AS)を付け忘れる | FROMの中のサブクエリには必ずエイリアスが必要(PostgreSQLの仕様) |
| ビューを作ったのに最新データが反映されない気がする | ビューは呼ぶたびに裏のSELECTが走るので必ず最新。気のせいか、マテリアライズドビューと混同しているか |