はじめに
SQLの副問い合わせの理解がかなり曖昧だったので、自分なりにまとめてみました。
SQL初学者の方のご参考になれば幸いです。
また、記事内容に誤りを発見された場合は教えていただけますととても有難いです。
なお、本記事はChatGPTの力を借りながら自分で作成しました。
副問い合わせとは
SQLの副問い合わせとは、SQL文の中に入れ子になって書かれる別のSELECT文のことです。
「サブクエリ」ともいいます。
副問い合わせの基本構造
SELECT 列名
FROM テーブル
WHERE 列名 = (SELECT ... FROM ...);
(SELECT ... FROM ...)
部分が副問い合わせ(サブクエリ)です。
例として、以下のような読書管理テーブルとジャンル管理テーブルがあったとします。
↓読書管理テーブル
ID | タイトル | 著者名 | ジャンルID | 読了日 | 金額 | お気に入り度 |
---|---|---|---|---|---|---|
1 | コンビニ人間 | 村田紗耶香 | 1 | 2025/9/15 | 693 | 3 |
2 | 十角館の殺人 | 綾辻行人 | 1 | 2025/9/20 | 946 | 2 |
3 | 傲慢と善良 | 辻村深月 | 1 | 2025/9/25 | 891 | 2 |
4 | ケーキの切れない非行少年たち | 宮口幸治 | 3 | 2025/9/30 | 836 | 2 |
5 | アルジャーノンに花束を | ダニエル・キイス | 2 | 2025/10/3 | 1320 | 3 |
↓ジャンル管理テーブル
ID | ジャンル |
---|---|
1 | 国内文学 |
2 | 海外文学 |
3 | 教養 |
4 | ノンフィクション |
5 | その他 |
読書管理テーブルで、購入金額が平均金額より高い本だけを抽出したい場合はどのようにすればいいでしょうか?
副問い合わせを使わない場合は、以下のように書きます
。
SELECT ROUND(AVG(金額)) FROM 読書管理; --(1)
/* (1)で得た金額「937」を条件式に記述する */
SELECT タイトル, 金額 FROM 読書管理
WHERE 金額 = 937; --(2)
副問い合わせを使うことで、(1)と(2)を1つのSQL文で書くことができます。
SELECT タイトル, 金額
FROM 読書管理
WHERE 金額 > (SELECT ROUND(AVG(金額)) FROM 読書管理);
副問い合わせの種類
副問い合わせには、以下の3種類があります。
①副問い合わせの結果が、単一の値となる「単一行副問い合わせ」
②副問い合わせの結果が、複数の値となる「複数行副問い合わせ」
③副問い合わせの結果が、n行m列の表形式になる「テーブル副問い合わせ(表副問い合わせ)」
単一行副問い合わせ
単一行副問い合わせとは、結果が1つの値だけ返ってくる副問い合わせのことです。
比較演算子(=, >, <, >=, <=, <>
)と一緒に使われます。
例 : 読書管理テーブルから、最も金額が高い本を取得する
SELECT タイトル, 金額
FROM 読書管理
WHERE 金額 = (
SELECT MAX(金額) FROM 読書管理
);
結果表
タイトル | 金額 |
---|---|
アルジャーノンに花束を | 1320 |
例 : 読書管理テーブルから、最も安い本以外の本を取得する
SELECT タイトル, 金額
FROM 読書管理
WHERE 金額 <> (
SELECT MIN(金額)
FROM 読書管理
);
結果表
タイトル | 金額 |
---|---|
十角館の殺人 | 946 |
傲慢と善良 | 891 |
ケーキの切れない非行少年たち | 836 |
アルジャーノンに花束を | 1320 |
複数行副問い合わせ
複数行副問い合わせとは、副問い合わせの結果が 複数行(n行1列) 返ってくる副問い合わせのことです。
IN / ANY / ALL / EXISTS
などと組み合わせて使います。
例 : ジャンルが「国内文学」と「教養」の本を取得する
SELECT タイトル, 金額, ジャンルID
FROM 読書管理
WHERE ジャンルID IN (
SELECT ID
FROM ジャンル管理
WHERE ジャンル IN ('国内文学', '教養')
);
結果表
タイトル | 金額 | ジャンルID |
---|---|---|
コンビニ人間 | 693 | 1 |
十角館の殺人 | 946 | 1 |
傲慢と善良 | 891 | 1 |
ケーキの切れない非行少年たち | 836 | 3 |
例 : 国内文学のいずれかの本よりも高い金額の本を取得する
SELECT タイトル, 金額
FROM 読書管理
WHERE 金額 > ANY (
SELECT 金額
FROM 読書管理
WHERE ジャンルID = 1
);
結果表
タイトル | 金額 |
---|---|
十角館の殺人 | 946 |
傲慢と善良 | 891 |
ケーキの切れない非行少年たち | 836 |
アルジャーノンに花束を | 1320 |
例 : 国内文学のすべての本よりも高い金額の本を探す
SELECT タイトル, 金額
FROM 読書管理
WHERE 金額 > ALL (
SELECT 金額
FROM 読書管理
WHERE ジャンルID = 1
);
結果表
タイトル | 金額 |
---|---|
アルジャーノンに花束を | 1320 |
テーブル副問い合わせ
テーブル副問い合わせとは、副問い合わせの結果をn行m列から成る1つの表(テーブル)のように扱う副問い合わせのことです。
FROM
と組み合わせて使います。
例 : ジャンルごとの平均金額を取得する
SELECT g.ジャンル, t.平均金額
FROM (
SELECT ジャンルID, ROUND(AVG(金額)) AS 平均金額
FROM 読書管理
GROUP BY ジャンルID
) AS t
JOIN ジャンル管理 AS g
ON t.ジャンルID = g.ID;
結果表
ジャンル | 平均金額 |
---|---|
国内文学 | 843 |
海外文学 | 1320 |
教養 | 836 |
処理手順
上記のテーブル副問い合わせでは、まず()内の副問い合わせを実行し、一時表「t」を取得します。
一時表とは、テーブル副問い合わせで結果を取得する為に、一時的に使われるテーブルのことです。
一時表「t」は以下のようになります。
ジャンルID | 平均金額 |
---|---|
1 | 843 |
2 | 1320 |
3 | 836 |
次に、ジャンル管理テーブルに「g」という別名を付け、一時表「t」と結合すると、ジャンルごとの平均金額が取得されます。
ジャンル | 平均金額 |
---|---|
国内文学 | 843 |
海外文学 | 1320 |
教養 | 836 |
例 : お気に入り度が最大の本をジャンルごとに取得する
SELECT g.ジャンル, t.タイトル, t.お気に入り度
FROM (
SELECT *
FROM 読書管理
WHERE お気に入り度 = (SELECT MAX(お気に入り度) FROM 読書管理)
) AS t
JOIN ジャンル管理 AS g
ON t.ジャンルID = g.ID;
結果表
ジャンル | タイトル | お気に入り度 |
---|---|---|
国内文学 | コンビニ人間 | 3 |
海外文学 | アルジャーノンに花束を | 3 |
処理手順
上記のテーブル副問い合わせでは、まず内側の単一行副問い合わせで最大のお気に入り度を取得します。
SELECT MAX(お気に入り度) FROM 読書管理;
/* MAX(お気に入り度) = 3 */
次に、テーブル副問い合わせでお気に入り度が最大の本を抽出し、一時表「t」とします。
SELECT *
FROM 読書管理
WHERE お気に入り度 = 3;
↓一時表「t」
ID | タイトル | 著者名 | ジャンルID | 読了日 | 金額 | お気に入り度 |
---|---|---|---|---|---|---|
1 | コンビニ人間 | 村田紗耶香 | 1 | 2025/9/15 | 693 | 3 |
5 | アルジャーノンに花束を | ダニエル・キイス | 2 | 2025/10/3 | 1320 | 3 |
最後に、一時表「t」をジャンル管理テーブルと結合することで、お気に入り度が最大の本をジャンルごとに取得できます。
ON t.ジャンルID = g.ID
ジャンル | タイトル | お気に入り度 |
---|---|---|
国内文学 | コンビニ人間 | 3 |
海外文学 | アルジャーノンに花束を | 3 |
副問い合わせとNULL
副問い合わせでNULLを扱う場合、いくつかの注意点があります。
そもそもNULLとは?
SQLでのNULLには、以下の特徴があります。
- SQLでの NULL は「値が存在しない」「不明な値」を意味する
- 0や空文字とは異なる
-
NULL = NULL
は TRUEにならない → 結果はUNKNOWN
副問い合わせでNULLを使う際の注意点
-
=
や<>
などの比較演算子は使えない
NULLは未知の値として扱われるため、比較した結果はFALSE
になります。 -
NULLを判定する場合は
IS NULL
/IS NOT NULL
を使う -
副問い合わせでNULLを返す可能性がある場合、
COALESCE
関数 でデフォルト値に置き換える
分かりやすくする為、読書管理テーブルに「車輪の下」を追加しました。
ID | タイトル | 著者名 | ジャンルID | 読了日 | 金額 | お気に入り度 |
---|---|---|---|---|---|---|
1 | コンビニ人間 | 村田紗耶香 | 1 | 2025/9/15 | 693 | 3 |
2 | 十角館の殺人 | 綾辻行人 | 1 | 2025/9/20 | 946 | 2 |
3 | 傲慢と善良 | 辻村深月 | 1 | 2025/9/25 | 891 | 2 |
4 | ケーキの切れない非行少年たち | 宮口幸治 | 3 | 2025/9/30 | 836 | 2 |
5 | アルジャーノンに花束を | ダニエル・キイス | 2 | 2025/10/3 | 1320 | 3 |
6 | 車輪の下 | ヘルマン・ヘッセ | NULL | 2025/10/10 | 539 | 3 |
例 : ジャンルIDがNULLの本を取得する
SELECT タイトル, 著者名, ジャンルID
FROM 読書管理
WHERE ジャンルID IS NULL;
結果表
タイトル | 著者名 | ジャンルID |
---|---|---|
車輪の下 | ヘルマン・ヘッセ | NULL |
例 : ジャンルIDがNULLの場合、デフォルト値を0として取得する
SELECT タイトル, 著者名, COALESCE(ジャンルID, 0) AS ジャンルID
FROM 読書管理
WHERE COALESCE(ジャンルID, 0) = 0;
結果表
タイトル | 著者名 | ジャンルID |
---|---|---|
車輪の下 | ヘルマン・ヘッセ | 0 |
WITH
WITH句は、副問い合わせに別名をつけて再利用できる仕組みです。
「共通テーブル式(CTE: Common Table Expression)」と呼ばれます。
WITH句の基本構文
WITH 別名 AS (
サブクエリ
)
SELECT ...
FROM 別名;
WITH句の特徴
- サブクエリを
WITH
で定義し、後のSELECT文でテーブルのように扱える - 複雑な副問い合わせを見やすく整理できる
- 再利用できる(1回定義すれば複数回参照可能)
例 : 読書管理テーブルで、ジャンルごとの平均金額を取得する
WITH 平均金額テーブル AS (
SELECT ジャンルID, ROUND(AVG(金額)) AS 平均金額
FROM 読書管理
GROUP BY ジャンルID
)
SELECT *
FROM 平均金額テーブル;
結果表
ジャンルID | 平均金額 |
---|---|
1 | 843 |
2 | 1320 |
3 | 836 |
処理手順
まず、「平均金額テーブル」という共通テーブル式を作る準備をします。
WITH 平均金額テーブル AS (
次に、内側の副問い合わせで、読書管理テーブルからジャンルごとの平均金額を計算し、四捨五入して「平均金額」という列を作ります。
これで、共通テーブル式「平均金額テーブル」の完成です。
SELECT ジャンルID, ROUND(AVG(金額)) AS 平均金額
FROM 読書管理
GROUP BY ジャンルID
最後に、平均金額テーブルから全ての列を取り出します。
SELECT *
FROM 平均金額テーブル;
例 : 読書管理テーブルで、平均金額より高い本を取得する
WITH 平均金額テーブル AS (
SELECT ROUND(AVG(金額)) AS 全体平均
FROM 読書管理
)
SELECT タイトル, 金額
FROM 読書管理, 平均金額テーブル
WHERE 読書管理.金額 > 平均金額テーブル.全体平均;
結果表
タイトル | 金額 |
---|---|
十角館の殺人 | 946 |
傲慢と善良 | 891 |
アルジャーノンに花束を | 1320 |
処理手順
まず、「平均金額テーブル」という共通テーブル式を作る準備をします。
WITH 平均金額テーブル AS (
次に、内側の副問い合わせで、読書管理テーブル全体の本の平均金額を計算して「全体平均」と名付けます。(中身は「全体平均」1行のみ)
SELECT ROUND(AVG(金額)) AS 全体平均
FROM 読書管理
読書管理テーブルと平均金額テーブルを一緒に扱います。
FROM 読書管理, 平均金額テーブル
金額が、全体平均を超えている本のみを抽出します。
WHERE 読書管理.金額 > 平均金額テーブル.全体平均;
参考書籍