— FROM句にサブクエリを使う
SELECT *
FROM (
SELECT product_name, price
FROM products
WHERE price >= 1000
) AS expensive_products;
この書き方をインラインビューまたは派生テーブル(Derived Table)と呼びます。
💡 インラインビューのイメージ
料理で例えると、「材料を下ごしらえしてから調理する」イメージです。
サブクエリ = 下ごしらえ(材料を切る、茹でるなど)
メインクエリ = 本調理(下ごしらえした材料を使って料理する)
先にデータを加工・集計してから、その結果に対してさらに処理を行うことができます。
1-3. FROM句のサブクエリの重要なルール
FROM句でサブクエリを使うとき、必ず守るべきルールがあります。
⚠️ 必須ルール:別名(エイリアス)をつける
FROM句のサブクエリには、必ず AS 別名 をつける必要があります。つけないとエラーになります!
※横にスクロールできます
— ❌ エラーになる例(別名がない)
SELECT *
FROM (
SELECT product_name, price FROM products
);
— エラー! Every derived table must have its own alias
— ✅ 正しい例(別名をつける)
SELECT *
FROM (
SELECT product_name, price FROM products
) AS product_list; — ← AS で別名をつける
— HAVINGを使う方法
SELECT category, AVG(price) as avg_price
FROM products
GROUP BY category
HAVING AVG(price) >= 2000;
解決策2: FROM句のサブクエリを使う
まず、カテゴリ別の集計を行うサブクエリを作ります。
※横にスクロールできます
— Step 1: カテゴリ別に集計(サブクエリ部分)
SELECT
category,
AVG(price) as avg_price,
COUNT(*) as product_count
FROM products
GROUP BY category;
このサブクエリの結果イメージ:
category
avg_price
product_count
家電
8,500
120
食品
500
250
家具
15,000
45
次に、この結果をFROM句に入れて、条件で絞り込みます。
※横にスクロールできます
— Step 2: 完成形
SELECT
category,
avg_price,
product_count
FROM (
— カテゴリ別に集計
SELECT
category,
AVG(price) as avg_price,
COUNT(*) as product_count
FROM products
GROUP BY category
) AS category_stats — 別名をつける
WHERE avg_price >= 2000 — 集計結果に対してWHEREが使える!
ORDER BY avg_price DESC;
クエリの解説:
サブクエリ: カテゴリ別の平均価格と商品数を計算
AS category_stats → サブクエリの結果に名前をつける
WHERE avg_price >= 2000 → サブクエリで計算した avg_price に対して条件をつける
— 顧客ごとに注文統計を計算
SELECT
customers.customer_id,
customers.customer_name,
COUNT(orders.order_id) as order_count,
SUM(orders.total_amount) as total_spent,
AVG(orders.total_amount) as avg_order_amount
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name;
クエリの解説:
LEFT JOIN → 注文がない顧客も含める
COUNT(orders.order_id) → 注文件数をカウント
SUM(orders.total_amount) → 総購入額を計算
AVG(orders.total_amount) → 平均注文額を計算
GROUP BY → 顧客ごとにグループ化
サブクエリの結果イメージ:
customer_id
customer_name
order_count
total_spent
avg_order_amount
1001
田中太郎
25
350,000
14,000
1002
鈴木花子
3
15,000
5,000
1003
佐藤次郎
15
180,000
12,000
Step 2: この結果をFROM句に入れて、VIP条件で絞り込む
※横にスクロールできます
— 完成形:VIP顧客を抽出
SELECT
customer_name,
order_count,
total_spent,
avg_order_amount
FROM (
— 顧客ごとに集計
SELECT
customers.customer_id,
customers.customer_name,
COUNT(orders.order_id) as order_count,
SUM(orders.total_amount) as total_spent,
AVG(orders.total_amount) as avg_order_amount
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name
) AS customer_stats
WHERE order_count >= 10 — 注文10回以上
AND total_spent >= 100000 — 総額10万円以上
ORDER BY total_spent DESC;
クエリの解説:
サブクエリ: 顧客ごとの統計を計算
AS customer_stats → サブクエリに「customer_stats」という名前をつける
WHERE order_count >= 10 AND total_spent >= 100000 → VIP条件で絞り込み
ORDER BY total_spent DESC → 購入額が多い順に並べる
なぜこの書き方が必要か:
COUNT() や SUM() の結果は、通常の WHERE 句では直接参照できません。FROM句のサブクエリを使うことで、集計結果を「普通の列」のように扱えるようになります。
— カテゴリ別の平均価格
SELECT
category,
AVG(price) as avg_price,
COUNT(*) as product_count
FROM products
GROUP BY category;
Step 2: その結果に対して全体の統計を計算
※横にスクロールできます
— 完成形:カテゴリ別平均価格の統計
SELECT
AVG(avg_price) as overall_avg_price, — 平均価格の平均
MAX(avg_price) as highest_category_avg, — 最も高いカテゴリの平均
MIN(avg_price) as lowest_category_avg, — 最も低いカテゴリの平均
COUNT(*) as category_count — カテゴリ数
FROM (
SELECT
category,
AVG(price) as avg_price,
COUNT(*) as product_count
FROM products
GROUP BY category
) AS category_averages
WHERE product_count >= 10; — 商品数10個以上のカテゴリのみ対象
— 月別売上と前月の売上を並べる
SELECT
order_month,
monthly_sales,
LAG(monthly_sales) OVER (ORDER BY order_month) as previous_month_sales
FROM (
SELECT
STRFTIME(‘%Y-%m’, order_date) as order_month,
SUM(total_amount) as monthly_sales
FROM orders
GROUP BY STRFTIME(‘%Y-%m’, order_date)
) AS monthly_totals;
LAG関数の解説:
LAG(monthly_sales) → 「1つ前の行」の monthly_sales を取得
OVER (ORDER BY order_month) → 月の順番で「前の行」を決定
最初の月は「前月」がないので、NULL になる
Step 3: 完成形(増加月だけを抽出)
※横にスクロールできます
— 完成形:前月より売上が増加した月
SELECT
order_month,
monthly_sales,
previous_month_sales,
monthly_sales – previous_month_sales as sales_diff
FROM (
SELECT
order_month,
monthly_sales,
LAG(monthly_sales) OVER (ORDER BY order_month) as previous_month_sales
FROM (
SELECT
STRFTIME(‘%Y-%m’, order_date) as order_month,
SUM(total_amount) as monthly_sales
FROM orders
GROUP BY STRFTIME(‘%Y-%m’, order_date)
) AS monthly_totals
) AS monthly_comparison
WHERE previous_month_sales IS NOT NULL — 前月データがある月のみ
AND monthly_sales > previous_month_sales — 売上が増加した月
ORDER BY order_month;
— サブクエリ1: 商品情報を集計
SELECT
category,
COUNT(*) as product_count,
AVG(price) as avg_price
FROM products
GROUP BY category;
Step 2: カテゴリ別の売上情報を集計
※横にスクロールできます
— サブクエリ2: 売上情報を集計
SELECT
products.category,
SUM(order_details.quantity * order_details.unit_price) as total_sales,
SUM(order_details.quantity) as total_quantity
FROM order_details
INNER JOIN products ON order_details.product_id = products.product_id
GROUP BY products.category;
Step 3: 2つのサブクエリを結合
※横にスクロールできます
— 完成形:2つのサブクエリをJOINで結合
SELECT
product_info.category,
product_info.product_count,
product_info.avg_price,
sales_info.total_sales,
sales_info.total_quantity
FROM (
— サブクエリ1: 商品情報を集計
SELECT
category,
COUNT(*) as product_count,
AVG(price) as avg_price
FROM products
GROUP BY category
) AS product_info
INNER JOIN (
— サブクエリ2: 売上情報を集計
SELECT
products.category,
SUM(order_details.quantity * order_details.unit_price) as total_sales,
SUM(order_details.quantity) as total_quantity
FROM order_details
INNER JOIN products ON order_details.product_id = products.product_id
GROUP BY products.category
) AS sales_info
ON product_info.category = sales_info.category
ORDER BY sales_info.total_sales DESC;
クエリの解説:
AS product_info → 商品情報のサブクエリに名前をつける
AS sales_info → 売上情報のサブクエリに名前をつける
INNER JOIN ... ON product_info.category = sales_info.category → カテゴリをキーに結合
それぞれのサブクエリの列を product_info.列名、sales_info.列名 で参照
実行結果のイメージ:
category
product_count
avg_price
total_sales
total_quantity
家電
120
8,500
15,000,000
3,500
食品
250
1,200
12,000,000
8,000
4-3. 例2:都道府県別の顧客数と売上を結合(LEFT JOIN)
やりたいこと:都道府県別の顧客数と、売上を結合して表示する。売上がない都道府県も含める
※横にスクロールできます
SELECT
customer_stats.prefecture,
customer_stats.customer_count,
COALESCE(order_stats.total_sales, 0) as total_sales,
COALESCE(order_stats.order_count, 0) as order_count
FROM (
— サブクエリ1: 都道府県別の顧客数
SELECT
prefecture,
COUNT(*) as customer_count
FROM customers
GROUP BY prefecture
) AS customer_stats
LEFT JOIN (
— サブクエリ2: 都道府県別の売上
SELECT
customers.prefecture,
SUM(orders.total_amount) as total_sales,
COUNT(orders.order_id) as order_count
FROM orders
INNER JOIN customers ON orders.customer_id = customers.customer_id
GROUP BY customers.prefecture
) AS order_stats
ON customer_stats.prefecture = order_stats.prefecture
ORDER BY total_sales DESC;
— 第1段階: 顧客別の購入額を集計
SELECT
customers.customer_id,
customers.customer_name,
COALESCE(SUM(orders.total_amount), 0) as total_spent
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name;
クエリの解説:
LEFT JOIN → 注文がない顧客も含める
COALESCE(..., 0) → 注文がない場合(NULL)は0にする
第1段階の結果イメージ:
customer_id
customer_name
total_spent
1001
田中太郎
350,000
1002
鈴木花子
75,000
1003
佐藤次郎
5,000
第2段階: 購入額に応じてランクを付ける
※横にスクロールできます
— 第2段階: ランクを付ける
SELECT
customer_name,
total_spent,
CASE
WHEN total_spent >= 100000 THEN ‘VIP’
WHEN total_spent >= 50000 THEN ‘プレミアム’
ELSE ‘一般’
END as customer_rank
FROM (
— 第1段階のサブクエリ
SELECT
customers.customer_id,
customers.customer_name,
COALESCE(SUM(orders.total_amount), 0) as total_spent
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name
) AS customer_totals;
CASE文の解説:
CASE WHEN ... THEN ... END → 条件分岐を行う
total_spent >= 100000 → 10万円以上なら「VIP」
total_spent >= 50000 → 5万円以上なら「プレミアム」
ELSE '一般' → それ以外は「一般」
第2段階の結果イメージ:
customer_name
total_spent
customer_rank
田中太郎
350,000
VIP
鈴木花子
75,000
プレミアム
佐藤次郎
5,000
一般
第3段階(完成形): ランクごとに集計する
※横にスクロールできます
— 完成形: ランク別の統計
SELECT
customer_rank,
COUNT(*) as customer_count,
ROUND(AVG(total_spent), 2) as avg_spent,
ROUND(MIN(total_spent), 2) as min_spent,
ROUND(MAX(total_spent), 2) as max_spent
FROM (
— 第2段階: ランクを付ける
SELECT
customer_name,
total_spent,
CASE
WHEN total_spent >= 100000 THEN ‘VIP’
WHEN total_spent >= 50000 THEN ‘プレミアム’
ELSE ‘一般’
END as customer_rank
FROM (
— 第1段階: 顧客別の購入額を集計
SELECT
customers.customer_id,
customers.customer_name,
COALESCE(SUM(orders.total_amount), 0) as total_spent
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name
) AS customer_totals
) AS ranked_customers
GROUP BY customer_rank
ORDER BY
CASE customer_rank
WHEN ‘VIP’ THEN 1
WHEN ‘プレミアム’ THEN 2
ELSE 3
END;
— ❌ 悪い例(インデントなし、1行)
SELECT * FROM (SELECT category, AVG(price) as avg_price FROM products GROUP BY category) AS stats WHERE avg_price >= 2000;
— ✅ 良い例(適切にインデント)
SELECT
category,
avg_price
FROM (
SELECT
category,
AVG(price) as avg_price
FROM products
GROUP BY category
) AS category_stats
WHERE avg_price >= 2000;
インデントのルール:
サブクエリの中身は、4スペース(またはタブ1つ)で字下げ
SELECT、FROM、WHERE、GROUP BYなどは行頭を揃える
長い行は適切な位置で改行する
6-2. コツ2:分かりやすい別名をつける
サブクエリの別名は、その内容を表す分かりやすい名前にしましょう。
※横にスクロールできます
— ❌ 悪い例(何のデータか不明)
SELECT * FROM (
SELECT * FROM products WHERE price > 1000
) AS t1;
SELECT * FROM (
SELECT category, AVG(price) FROM products GROUP BY category
) AS x;
— ✅ 良い例(意味が明確)
SELECT * FROM (
SELECT * FROM products WHERE price > 1000
) AS high_price_products;
SELECT * FROM (
SELECT category, AVG(price) as avg_price FROM products GROUP BY category
) AS category_price_stats;
💡 良い別名の例:
customer_stats → 顧客の統計
monthly_sales → 月別売上
top_products → 上位商品
category_averages → カテゴリ別平均
ranked_customers → ランク付けされた顧客
6-3. コツ3:コメントを活用する
複雑なクエリには、コメントで説明を加えましょう。
※横にスクロールできます
— 顧客の購入傾向を分析するクエリ
SELECT
customer_rank,
COUNT(*) as customer_count
FROM (
— 購入額でランク分け
SELECT
customer_name,
CASE
WHEN total_spent >= 100000 THEN ‘VIP’
WHEN total_spent >= 50000 THEN ‘プレミアム’
ELSE ‘一般’
END as customer_rank
FROM (
— 顧客別の総購入額を計算
SELECT
customers.customer_name,
SUM(orders.total_amount) as total_spent
FROM customers
INNER JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name
) AS customer_spending
) AS ranked_customers
GROUP BY customer_rank;
コメントのコツ:
サブクエリの目的を簡潔に書く
複雑な計算には何を計算しているかを書く
全体の冒頭にクエリの目的を書く
ただし、過度なコメントは逆に読みにくくなるので注意
6-4. コツ4:必要な列だけを選択する
SELECT * は便利ですが、サブクエリでは必要な列だけを選択しましょう。
※横にスクロールできます
— ❌ 悪い例(不要な列も取得)
SELECT *
FROM (
SELECT *
FROM products
WHERE price > 1000
) AS expensive;
— ✅ 良い例(必要な列だけ)
SELECT
product_name,
price,
category
FROM (
SELECT
product_name,
price,
category
FROM products
WHERE price > 1000
) AS expensive_products;
SELECT
product_name,
price,
category
FROM (
SELECT
product_name,
price,
category
FROM products
WHERE price >= 1000
) AS high_price_products
ORDER BY price DESC
LIMIT 5;
解説:
サブクエリで1000円以上の商品を抽出
AS high_price_products で別名をつける(必須)
メインクエリで価格順に並べてトップ5を取得
問題 2基本
カテゴリ別の平均価格
カテゴリ別の平均価格を計算し、平均価格が1500円以上のカテゴリだけを表示してください。
※横にスクロールできます
SELECT
category,
avg_price,
product_count
FROM (
SELECT
category,
AVG(price) as avg_price,
COUNT(*) as product_count
FROM products
GROUP BY category
) AS category_stats
WHERE avg_price >= 1500
ORDER BY avg_price DESC;
SELECT
customer_name,
order_count,
total_spent
FROM (
SELECT
customers.customer_name,
COUNT(orders.order_id) as order_count,
SUM(orders.total_amount) as total_spent
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name
) AS customer_stats
WHERE order_count >= 10
AND total_spent >= 50000
ORDER BY total_spent DESC;
SELECT
product_name,
price,
cost,
profit_rate
FROM (
SELECT
product_name,
price,
cost,
ROUND((price – cost) * 100.0 / cost, 2) as profit_rate
FROM products
WHERE cost > 0
) AS profit_analysis
WHERE profit_rate >= 30
ORDER BY profit_rate DESC;
解説:
WHERE cost > 0 → 0で割るエラーを防ぐ
* 100.0 → 小数点を含む計算にするため(整数同士の割り算を避ける)
ROUND(..., 2) → 小数点以下2桁に丸める
問題 5応用
カテゴリ別の商品数と売上
カテゴリ別の商品数と総売上を取得してください。2つのサブクエリを結合して実現してください。
※横にスクロールできます
SELECT
product_stats.category,
product_stats.product_count,
COALESCE(sales_stats.total_sales, 0) as total_sales
FROM (
— サブクエリ1: 商品数を集計
SELECT
category,
COUNT(*) as product_count
FROM products
GROUP BY category
) AS product_stats
LEFT JOIN (
— サブクエリ2: 売上を集計
SELECT
products.category,
SUM(order_details.quantity * order_details.unit_price) as total_sales
FROM order_details
INNER JOIN products ON order_details.product_id = products.product_id
GROUP BY products.category
) AS sales_stats
ON product_stats.category = sales_stats.category
ORDER BY total_sales DESC;
解説:
2つのサブクエリで別々に集計(商品数と売上)
LEFT JOIN → 売上がないカテゴリも表示
COALESCE(..., 0) → NULLを0に置き換え
問題 6チャレンジ
月別売上の前月比
月別の売上を計算し、前月と比較して売上が増加した月だけを表示してください。
※横にスクロールできます
SELECT
order_month,
monthly_sales,
previous_month_sales,
monthly_sales – previous_month_sales as sales_diff
FROM (
SELECT
order_month,
monthly_sales,
LAG(monthly_sales) OVER (ORDER BY order_month) as previous_month_sales
FROM (
SELECT
STRFTIME(‘%Y-%m’, order_date) as order_month,
SUM(total_amount) as monthly_sales
FROM orders
GROUP BY STRFTIME(‘%Y-%m’, order_date)
) AS monthly_totals
) AS monthly_comparison
WHERE previous_month_sales IS NOT NULL
AND monthly_sales > previous_month_sales
ORDER BY order_month;
解説:
第1段階: 月別売上を集計
第2段階: LAG関数で前月の売上を取得
メインクエリ: 前月より増加した月だけを抽出
previous_month_sales IS NOT NULL → 最初の月(前月データなし)を除外
SELECT
customer_rank,
COUNT(*) as customer_count,
ROUND(AVG(total_spent), 2) as avg_spent,
ROUND(MIN(total_spent), 2) as min_spent,
ROUND(MAX(total_spent), 2) as max_spent
FROM (
SELECT
customer_name,
total_spent,
CASE
WHEN total_spent >= 100000 THEN ‘VIP’
WHEN total_spent >= 50000 THEN ‘プレミアム’
ELSE ‘一般’
END as customer_rank
FROM (
SELECT
customers.customer_name,
COALESCE(SUM(orders.total_amount), 0) as total_spent
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
GROUP BY customers.customer_id, customers.customer_name
) AS customer_totals
) AS ranked_customers
GROUP BY customer_rank
ORDER BY
CASE customer_rank
WHEN ‘VIP’ THEN 1
WHEN ‘プレミアム’ THEN 2
ELSE 3
END;
解説:
第1段階: 顧客別の購入額を集計
第2段階: CASE文でランクを付与
メインクエリ: ランクでGROUP BY
ORDER BY CASE → VIP、プレミアム、一般の順で表示
問題 8チャレンジ
都道府県別の顧客数と売上
都道府県別の顧客数、注文数、総売上を表示してください。売上が多い順に並べてください。
※横にスクロールできます
SELECT
customer_stats.prefecture,
customer_stats.customer_count,
COALESCE(order_stats.order_count, 0) as order_count,
COALESCE(order_stats.total_sales, 0) as total_sales
FROM (
— 都道府県別の顧客数
SELECT
prefecture,
COUNT(*) as customer_count
FROM customers
GROUP BY prefecture
) AS customer_stats
LEFT JOIN (
— 都道府県別の注文数と売上
SELECT
customers.prefecture,
COUNT(orders.order_id) as order_count,
SUM(orders.total_amount) as total_sales
FROM orders
INNER JOIN customers ON orders.customer_id = customers.customer_id
GROUP BY customers.prefecture
) AS order_stats
ON customer_stats.prefecture = order_stats.prefecture
ORDER BY total_sales DESC;
解説:
2つのサブクエリで顧客統計と注文統計を別々に集計
LEFT JOINで全都道府県を表示(注文がない地域も含む)
COALESCEでNULLを0に変換
📝 Step 3 のまとめ
✅ 学んだこと
FROM句のサブクエリ(インラインビュー)を使える
サブクエリには必ず別名(AS)をつける
集計結果を再利用して、さらに処理できる
複数のサブクエリをJOINで結合できる
サブクエリを入れ子にして多段階の処理ができる
可読性を保つためのコツを理解した
📌 FROM句のサブクエリの基本パターン
※横にスクロールできます
— 基本形
SELECT 列名
FROM (
SELECT 列名
FROM テーブル
WHERE 条件
) AS サブクエリ別名
WHERE 追加条件;
— 複数のサブクエリを結合
SELECT *
FROM (サブクエリ1) AS 別名1
JOIN (サブクエリ2) AS 別名2
ON 結合条件;
— 入れ子(2段階)
SELECT *
FROM (
SELECT *
FROM (
サブクエリ
) AS 内側の別名
) AS 外側の別名;
💡 実務での使いどころ
集計の集計: カテゴリ別平均の全体平均など
段階的な絞り込み: 複雑な条件を分解して処理
複数統計の結合: 異なる集計結果を組み合わせる
計算列へのフィルタ: 利益率など計算した値で絞り込む
ランキング処理: トップNの抽出、ランク別統計
⚠️ 注意点
サブクエリには必ず別名をつける(つけないとエラー)
入れ子は3段階までを目安に
インデントで構造を分かりやすくする
必要な列だけを選択する
複雑すぎる場合は分割を検討(WITH句、ビューなど)
❓ よくある質問
Q1: サブクエリに別名をつけないとどうなりますか?
エラーになります!「Every derived table must have its own alias」というエラーメッセージが表示されます。FROM句のサブクエリには、必ず AS 別名 をつけてください。