SQLのサブクエリとは?内側のSELECTと実行結果から学ぶ使い方
サブクエリは、SQLの中に書く別のSELECT文です。「平均金額より高い注文」のように、別の検索結果を条件に使えます。まず内側のSELECTだけを実行し、その値を外側でどう使うかを見てみましょう。
例で使う表
customersは顧客の表、ordersは注文の表です。orders.customer_idで注文した顧客が分かります。青木商店には2件、北野物産には1件の注文があり、中央商事には注文がありません。どの例もこの2つの表を使います。結果表の行の並びは一例です。ORDER BYで指定しない限り、表示順は保証されません。
| id(顧客ID) | name(顧客名) |
|---|---|
| 1 | 青木商店 |
| 2 | 北野物産 |
| 3 | 中央商事 |
| id(注文ID) | customer_id(顧客ID) | amount(金額) |
|---|---|---|
| 101 | 1 | 12000 |
| 102 | 1 | 8000 |
| 103 | 2 | 25000 |
サブクエリの基本構文
SELECT id, amount
FROM orders
WHERE amount > (
SELECT AVG(amount)
FROM orders
);まず内側のSELECTで平均注文額を求める
注文額の合計は12000 + 8000 + 25000で45000円です。AVG(amount)で3件の平均を求めると15000円になります。この1行1列の結果を、次の節で比較の基準に使います。
SELECT AVG(amount) AS average_amount
FROM orders;| average_amount |
|---|
| 15000 |
求めた値を条件にして注文を探す
平均の15000円より高い注文を、いったん数値を直接書いて探します。12000円と8000円は外れ、25000円の注文103だけが残ります。このSQLでは、平均が変わるたびに15000を書き直す必要があります。
SELECT id, amount
FROM orders
WHERE amount > 15000;| id | amount |
|---|---|
| 103 | 25000 |
数値の代わりにサブクエリを入れる
前の15000を、平均を求めるSELECTで置き換えます。括弧の中がサブクエリです。今の表では平均15000円と比較するので同じ注文103が残りますが、注文が増えても、そのときの平均が比較に使われます。
SELECT id, amount
FROM orders
WHERE amount > (
SELECT AVG(amount)
FROM orders
);| id | amount |
|---|---|
| 103 | 25000 |
INで複数行の候補を条件にする
注文した顧客のIDを内側で取り出すと、1、1、2になります。INはその候補にcustomers.idが含まれるかを調べます。青木商店と北野物産が残り、中央商事は外れます。候補に1が2回あっても、青木商店の結果は1行です。
SELECT id, name
FROM customers
WHERE id IN (
SELECT customer_id
FROM orders
);| id | name |
|---|---|
| 2 | 北野物産 |
| 1 | 青木商店 |
FROMにサブクエリを置いて集計表として使う
内側では注文を顧客IDごとに数えます。顧客1が2件、顧客2が1件の集計表です。この表にtotalsという別名を付け、外側のWHEREで2件以上を選びます。結果は顧客1の1行です。今回はHAVINGでも書けますが、検索結果を表として使う形を確認しましょう。
SELECT customer_id, order_count
FROM (
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
) AS totals
WHERE order_count >= 2;| customer_id | order_count |
|---|---|
| 1 | 2 |
EXISTSでその顧客の注文があるかを調べる
内側のWHEREが、外側のcustomers.idを参照しています。顧客1には2行、顧客2には1行の注文が見つかるので、どちらも残ります。EXISTSは1行でも見つかれば成立します。SELECT 1は存在確認用の書き方で、注文数を数えているわけではありません。
SELECT customers.id, customers.name
FROM customers
WHERE EXISTS (
SELECT 1
FROM orders
WHERE orders.customer_id = customers.id
);| id | name |
|---|---|
| 2 | 北野物産 |
| 1 | 青木商店 |
NOT EXISTSで注文がない顧客を選ぶ
EXISTSの前にNOTを付けると、内側に1行も見つからない顧客を選べます。顧客3の中央商事だけが残ります。前の例と同じく、orders.customer_id = customers.idが「その顧客の注文」を調べるための条件です。
SELECT customers.id, customers.name
FROM customers
WHERE NOT EXISTS (
SELECT 1
FROM orders
WHERE orders.customer_id = customers.id
);| id | name |
|---|---|
| 3 | 中央商事 |
実際に1問解いてみる
スカラーサブクエリ
全社員の平均給与を超える社員を取得してください。行順は問いません。
テーブル
読み込み中…
実行結果
関連構文
サブクエリについてよくある質問
- サブクエリが複数行を返すとエラーになるのはなぜですか?
- amount > (SELECT ...)の右側のように1つの値が必要な場所では、1列・最大1行である必要があります。複数の候補と照合したい場合はINを使います。0行の場合は値がNULLになります。
- 相関サブクエリとは何ですか?
- 本文のEXISTSの例のように、内側のSQLが外側の行の値を参照するサブクエリです。顧客ごとに注文の有無を考えると意味を追えますが、実際の実行手順はデータベースが決めます。
参考文献
内容はPostgreSQL公式ドキュメントを参照し、初めて学ぶ人向けに日本語で整理しています。
- PostgreSQL公式:スカラーサブクエリ — スカラーサブクエリの仕様と使用例
- PostgreSQL公式:FROM内のサブクエリ — FROM内のサブクエリの仕様と使用例
- PostgreSQL公式:IN・EXISTSとサブクエリ — IN・EXISTSとサブクエリの仕様と使用例