Step 12:CASE文と条件分岐

🔀 Step 12: CASE文と条件分岐

複雑な条件判定をマスターしよう!

📋 このステップで学ぶこと
  • CASE式とは何か(SQLの「if文」)
  • 検索CASE式と単純CASE式の違い
  • ピボット変換(行を列に展開)
  • 条件付き集計(SUM + CASE、COUNT + CASE)
  • 実務での活用パターン

🎯 1. CASE式とは?

1-1. SQLの「if文」

プログラミング言語には if文(条件分岐)がありますよね。SQLにも同じような機能があり、それが CASE式 です。

💡 プログラミングとの比較
プログラミング(Python) SQL(CASE式)
if price >= 1000:
  return ‘高価格’
elif price >= 500:
  return ‘中価格’
else:
  return ‘低価格’
CASE
  WHEN price >= 1000 THEN ‘高価格’
  WHEN price >= 500 THEN ‘中価格’
  ELSE ‘低価格’
END

if / elif / else が WHEN / WHEN / ELSE に対応しています。

1-2. CASE式でできること

CASE式の主な用途:
  • 値の変換:数値を文字に変換(1→’小’, 2→’中’, 3→’大’)
  • 分類・ランク付け:範囲でグループ分け(0-19歳→’10代’)
  • 計算の切り替え:条件によって計算方法を変える
  • NULL処理:NULLを別の値に置き換え
  • ピボット変換:行データを列データに変換
  • 条件付き集計:特定の条件だけをカウント・合計

1-3. CASE式の基本構文

📌 CASE式の基本形

※横にスクロールできます

CASE WHEN 条件1 THEN 結果1 WHEN 条件2 THEN 結果2 WHEN 条件3 THEN 結果3 ELSE デフォルト値 END

各部分の意味:

  • CASE → CASE式の開始
  • WHEN 条件 THEN 結果 → 条件が真なら結果を返す
  • ELSE デフォルト値 → どの条件にも当てはまらない場合
  • END → CASE式の終了(必須!

📋 2. CASE式の評価順序

2-1. 上から順に評価される

CASE式は 上から順に条件を評価 し、最初に当てはまった条件 で結果が決まります。それ以降の条件は評価されません。

📌 順序が重要な理由

例えば、価格を「高価格」「中価格」「低価格」に分類する場合:

順序 条件 price = 1500 の場合
1番目 price >= 1000 ✅ 当てはまる → ‘高価格’
2番目 price >= 500 (評価されない)
3番目 ELSE (評価されない)

1500円は「>= 1000」にも「>= 500」にも当てはまりますが、最初に当てはまった「高価格」が結果になります。

2-2. 厳しい条件を先に書く

❌ 間違った順序(緩い条件が先):

※横にスクロールできます

— 間違い: すべて「低価格」になってしまう CASE WHEN price >= 100 THEN ‘低価格’ — ほぼ全部ここに当てはまる! WHEN price >= 500 THEN ‘中価格’ — 評価されない WHEN price >= 1000 THEN ‘高価格’ — 評価されない END
✅ 正しい順序(厳しい条件が先):

※横にスクロールできます

— 正しい: 厳しい条件を先に CASE WHEN price >= 1000 THEN ‘高価格’ — 1000以上だけ WHEN price >= 500 THEN ‘中価格’ — 500以上1000未満 ELSE ‘低価格’ — 500未満 END

2-3. ELSEの役割

ELSE は「どの条件にも当てはまらなかった場合」のデフォルト値を指定します。

ELSEを省略すると:

ELSEを省略した場合、どの条件にも当てはまらないと NULL が返されます。想定外の値への対応として、ELSEを書くことを推奨します。

🔰 3. 簡単な例で理解する

3-1. 例1:価格で商品を分類する

やりたいこと:商品を価格帯で「高価格」「中価格」「低価格」に分類する

※横にスクロールできます

SELECT product_name, price, CASE WHEN price >= 1000 THEN ‘高価格’ WHEN price >= 500 THEN ‘中価格’ ELSE ‘低価格’ END AS price_category FROM products ORDER BY price DESC LIMIT 10;
クエリの解説:
  • CASE ... END → CASE式全体
  • WHEN price >= 1000 THEN '高価格' → 1000円以上なら「高価格」
  • WHEN price >= 500 THEN '中価格' → 500円以上1000円未満なら「中価格」
  • ELSE '低価格' → それ以外(500円未満)は「低価格」
  • AS price_category → CASE式の結果に列名をつける
結果イメージ:
product_name price price_category
高級メロン 5,000 高価格
まぐろ 3,000 高価格
ぶどう 500 中価格
りんご 150 低価格
バナナ 120 低価格

3-2. 例2:在庫状況を判定する

やりたいこと:在庫数に応じて「在庫切れ」「在庫少」「通常」「在庫豊富」を表示する

※横にスクロールできます

SELECT product_name, stock, CASE WHEN stock = 0 THEN ‘在庫切れ’ WHEN stock < 10 THEN '在庫少' WHEN stock < 50 THEN '通常' ELSE '在庫豊富' END AS stock_status FROM products ORDER BY stock;
ポイント:
  • stock = 0 を最初に書く(最も厳しい条件)
  • stock < 10 は「1〜9」に当てはまる(0は除外済み)
  • stock < 50 は「10〜49」に当てはまる
  • ELSE は「50以上」に当てはまる

3-3. CASE式が使える場所

CASE式は SELECT句だけでなく、様々な場所で使えます

📌 CASE式が使える場所
場所 用途
SELECT句 新しい列を作る CASE ... END AS 列名
WHERE句 条件で絞り込み WHERE CASE ... END = '値'
ORDER BY句 動的な並び替え ORDER BY CASE ... END
GROUP BY句 条件でグループ化 GROUP BY CASE ... END
集計関数内 条件付き集計 SUM(CASE ... END)

3-4. 例3:ORDER BYでCASE式を使う

やりたいこと:在庫切れを最優先で表示する(通常の数値順ではなく)

※横にスクロールできます

SELECT product_name, stock, CASE WHEN stock = 0 THEN '在庫切れ' WHEN stock < 10 THEN '在庫少' ELSE '正常' END AS stock_status FROM products ORDER BY CASE WHEN stock = 0 THEN 1 -- 在庫切れを1番に WHEN stock < 10 THEN 2 -- 在庫少を2番に ELSE 3 -- 正常を3番に END, product_name;
ポイント:
  • ORDER BY でCASE式を使うと、優先度順に並び替えできる
  • 数値(1, 2, 3)を返すことで順序を制御
  • 同じ優先度内では product_name でソート

🔍 4. 検索CASE式(自由な条件を書ける)

4-1. 検索CASE式とは

検索CASE式は、各 WHEN自由な条件式を書けるタイプです。これが最も一般的に使われる形です。

📌 検索CASE式の構文

※横にスクロールできます

CASE WHEN 条件式1 THEN 結果1 WHEN 条件式2 THEN 結果2 WHEN 条件式3 THEN 結果3 ELSE デフォルト値 END

条件式には =><BETWEENINANDOR など、何でも使えます。

4-2. 例1:季節を判定する

やりたいこと:注文日の月から季節を判定する(12-2月→冬、3-5月→春、6-8月→夏、9-11月→秋)

※横にスクロールできます

SELECT order_id, order_date, CASE WHEN strftime('%m', order_date) IN ('12', '01', '02') THEN '冬' WHEN strftime('%m', order_date) IN ('03', '04', '05') THEN '春' WHEN strftime('%m', order_date) IN ('06', '07', '08') THEN '夏' ELSE '秋' END AS season FROM orders ORDER BY order_date DESC LIMIT 10;
クエリの解説:
  • strftime('%m', order_date) → 日付から月を抽出('01'〜'12')
  • IN ('12', '01', '02') → 12月、1月、2月のいずれか
  • 複数の値をまとめて判定するのに IN が便利

4-3. 例2:顧客ランクを判定する

やりたいこと:顧客の総購入額でランクを判定する

コードを段階的に見ていきましょう。

ステップ1:顧客ごとの総購入額を計算

※横にスクロールできます

SELECT c.customer_id, c.customer_name, SUM(o.total_amount) AS total_purchase FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.customer_name

ステップ2:総購入額でランクを判定

※横にスクロールできます

CASE WHEN SUM(o.total_amount) >= 100000 THEN 'プラチナ' WHEN SUM(o.total_amount) >= 50000 THEN 'ゴールド' WHEN SUM(o.total_amount) >= 10000 THEN 'シルバー' ELSE 'ブロンズ' END AS customer_rank
ポイント:
  • CASE式の条件に集計関数(SUM)を使える
  • 厳しい条件(>= 100000)を先に書く

完成コード:

※横にスクロールできます

SELECT c.customer_name AS 顧客名, COALESCE(SUM(o.total_amount), 0) AS 総購入額, CASE WHEN SUM(o.total_amount) >= 100000 THEN 'プラチナ' WHEN SUM(o.total_amount) >= 50000 THEN 'ゴールド' WHEN SUM(o.total_amount) >= 10000 THEN 'シルバー' ELSE 'ブロンズ' END AS 顧客ランク FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.customer_name ORDER BY 総購入額 DESC NULLS LAST;
結果イメージ:
顧客名 総購入額 顧客ランク
山田太郎 150,000 プラチナ
鈴木花子 75,000 ゴールド
田中次郎 25,000 シルバー
佐藤美咲 5,000 ブロンズ

4-4. 例3:複数条件を組み合わせる

やりたいこと:在庫と価格を組み合わせて優先度を判定する

※横にスクロールできます

SELECT product_name AS 商品名, price AS 価格, stock AS 在庫, CASE WHEN stock = 0 THEN '販売停止' WHEN stock < 5 AND price >= 1000 THEN '高級品・要補充' WHEN stock < 10 THEN '補充推奨' WHEN stock > 100 THEN '過剰在庫注意' ELSE '正常' END AS ステータス FROM products ORDER BY stock;
ポイント:
  • AND で複数条件を組み合わせられる
  • stock < 5 AND price >= 1000 → 在庫5未満 かつ 1000円以上
  • 条件の順序に注意(stock = 0 を最初に)

📝 5. 単純CASE式(値を直接比較)

5-1. 単純CASE式とは

単純CASE式は、1つの値と複数の値を = で比較するタイプです。検索CASE式より簡潔に書けます

📌 単純CASE式の構文

※横にスクロールできます

CASE 列名 WHEN 値1 THEN 結果1 WHEN 値2 THEN 結果2 WHEN 値3 THEN 結果3 ELSE デフォルト値 END

CASE の直後に列名を書き、各 WHEN で値だけを書きます。

5-2. 検索CASE式と単純CASE式の比較

同じ処理を両方で書いた場合:
検索CASE式 単純CASE式
CASE
  WHEN category = 'fruit' THEN '果物'
  WHEN category = 'vegetable' THEN '野菜'
  ELSE 'その他'
END
CASE category
  WHEN 'fruit' THEN '果物'
  WHEN 'vegetable' THEN '野菜'
  ELSE 'その他'
END

単純CASE式の方が短く書けます。category = を省略できます。

5-3. 例1:カテゴリを日本語に変換

やりたいこと:英語のカテゴリコードを日本語に変換する

※横にスクロールできます

SELECT product_name AS 商品名, category AS カテゴリコード, CASE category WHEN 'fruit' THEN '果物' WHEN 'vegetable' THEN '野菜' WHEN 'meat' THEN '肉類' WHEN 'fish' THEN '魚類' ELSE 'その他' END AS カテゴリ名 FROM products LIMIT 10;
結果イメージ:
商品名 カテゴリコード カテゴリ名
りんご fruit 果物
バナナ fruit 果物
キャベツ vegetable 野菜
牛肉 meat 肉類

5-4. 例2:曜日番号を曜日名に変換

やりたいこと:strftime('%w')で得られる曜日番号(0-6)を曜日名に変換する

※横にスクロールできます

SELECT order_date AS 注文日, strftime('%w', order_date) AS 曜日番号, CASE strftime('%w', order_date) WHEN '0' THEN '日曜日' WHEN '1' THEN '月曜日' WHEN '2' THEN '火曜日' WHEN '3' THEN '水曜日' WHEN '4' THEN '木曜日' WHEN '5' THEN '金曜日' WHEN '6' THEN '土曜日' END AS 曜日名 FROM orders ORDER BY order_date DESC LIMIT 7;
ポイント:
  • strftime('%w', order_date) → 曜日番号('0'=日曜〜'6'=土曜)
  • 戻り値は文字列なので、WHEN も '0' のように文字列で比較
  • ELSEがなくても、0-6のすべてをカバーしているのでOK

5-5. 例3:ステータスコードを文字に変換

やりたいこと:数値のステータスコードを人間が読める形に変換する

※横にスクロールできます

SELECT order_id AS 注文ID, status AS ステータスコード, CASE status WHEN 0 THEN '未処理' WHEN 1 THEN '処理中' WHEN 2 THEN '発送済' WHEN 3 THEN '配達完了' WHEN 9 THEN 'キャンセル' ELSE '不明' END AS ステータス名 FROM orders LIMIT 10;
活用シーン:
  • マスターデータの変換:コード → 名称
  • レポート作成:英語 → 日本語
  • フラグの解釈:0/1 → '無効'/'有効'

⚖️ 6. 検索CASE式と単純CASE式の使い分け

📌 使い分けの基準
タイプ 使う場面
単純CASE式 値の変換(= での比較) コード → 名称、ID → 名前
検索CASE式 範囲判定、複雑な条件 >= 1000、AND/OR、IN

迷ったら検索CASE式でOK! 検索CASE式はどんな条件でも書けるので、汎用性が高いです。

⚠️ 単純CASE式の制限

単純CASE式は = での比較のみです。以下はできません:

  • ❌ 範囲判定(>= 1000
  • ❌ NULL判定(IS NULL
  • ❌ 複数条件(ANDOR
  • ❌ パターンマッチ(LIKE

これらが必要な場合は検索CASE式を使ってください。

🔄 7. ピボット変換(行を列に)

7-1. ピボット変換とは

ピボット変換とは、行データを列データに変換することです。Excelのピボットテーブルと同じ考え方です。

💡 ピボット変換のイメージ

変換前(行データ):

売上
2024 1月 100万
2024 2月 120万
2024 3月 150万

変換後(列データ):

1月 2月 3月
2024 100万 120万 150万

月が「行」から「列」に変換されています。レポートでは列形式の方が見やすいことが多いです。

7-2. ピボット変換の基本パターン

📌 ピボット変換の公式

※横にスクロールできます

SELECT グループ化する列, SUM(CASE WHEN 条件1 THEN 値 ELSE 0 END) AS 列名1, SUM(CASE WHEN 条件2 THEN 値 ELSE 0 END) AS 列名2, SUM(CASE WHEN 条件3 THEN 値 ELSE 0 END) AS 列名3 FROM テーブル GROUP BY グループ化する列;

ポイント: 「条件に合う場合だけ値を足す、合わない場合は0」という処理を列ごとに行います。

7-3. 例1:月別売上を横に並べる

やりたいこと:2024年の1月〜6月の売上を列に展開する

コードを段階的に見ていきましょう。

ステップ1:1月の売上だけを取り出す

※横にスクロールできます

SUM(CASE WHEN strftime('%m', order_date) = '01' THEN total_amount ELSE 0 END) AS 一月
解説:
  • strftime('%m', order_date) = '01' → 1月の場合
  • THEN total_amount → 売上金額を返す
  • ELSE 0 → 1月以外は0
  • SUM(...) → 1月の売上だけが合計される

ステップ2:2月〜6月も同様に

※横にスクロールできます

SUM(CASE WHEN strftime('%m', order_date) = '02' THEN total_amount ELSE 0 END) AS 二月, SUM(CASE WHEN strftime('%m', order_date) = '03' THEN total_amount ELSE 0 END) AS 三月, -- 以下同様...

完成コード:

※横にスクロールできます

SELECT strftime('%Y', order_date) AS 年, SUM(CASE WHEN strftime('%m', order_date) = '01' THEN total_amount ELSE 0 END) AS 一月, SUM(CASE WHEN strftime('%m', order_date) = '02' THEN total_amount ELSE 0 END) AS 二月, SUM(CASE WHEN strftime('%m', order_date) = '03' THEN total_amount ELSE 0 END) AS 三月, SUM(CASE WHEN strftime('%m', order_date) = '04' THEN total_amount ELSE 0 END) AS 四月, SUM(CASE WHEN strftime('%m', order_date) = '05' THEN total_amount ELSE 0 END) AS 五月, SUM(CASE WHEN strftime('%m', order_date) = '06' THEN total_amount ELSE 0 END) AS 六月, SUM(total_amount) AS 合計 FROM orders WHERE strftime('%Y', order_date) = '2024' GROUP BY strftime('%Y', order_date);
結果イメージ:
一月 二月 三月 四月 五月 六月 合計
2024 1,000,000 1,200,000 1,500,000 1,800,000 1,600,000 2,000,000 9,100,000

7-4. 例2:カテゴリ別売上のピボット

やりたいこと:月ごとに、カテゴリ別の売上を列に展開する

※横にスクロールできます

SELECT strftime('%Y-%m', o.order_date) AS 年月, SUM(CASE WHEN p.category = '果物' THEN o.total_amount ELSE 0 END) AS 果物, SUM(CASE WHEN p.category = '野菜' THEN o.total_amount ELSE 0 END) AS 野菜, SUM(CASE WHEN p.category = '肉類' THEN o.total_amount ELSE 0 END) AS 肉類, SUM(o.total_amount) AS 合計 FROM orders o JOIN products p ON o.product_id = p.product_id GROUP BY strftime('%Y-%m', o.order_date) ORDER BY 年月 DESC LIMIT 6;
結果イメージ:
年月 果物 野菜 肉類 合計
2024-06 50,000 30,000 80,000 160,000
2024-05 45,000 28,000 75,000 148,000
2024-04 40,000 25,000 70,000 135,000

📊 8. 条件付き集計

8-1. SUM + CASE:条件付き合計

SUM(CASE ...) を使うと、特定の条件を満たす値だけを合計できます。

例:平日と週末の売上を別々に集計

※横にスクロールできます

SELECT strftime('%Y-%m', order_date) AS 年月, SUM(total_amount) AS 総売上, SUM(CASE WHEN strftime('%w', order_date) NOT IN ('0', '6') THEN total_amount ELSE 0 END) AS 平日売上, SUM(CASE WHEN strftime('%w', order_date) IN ('0', '6') THEN total_amount ELSE 0 END) AS 週末売上 FROM orders GROUP BY strftime('%Y-%m', order_date) ORDER BY 年月 DESC LIMIT 6;
クエリの解説:
  • strftime('%w', order_date) → 曜日番号('0'=日曜、'6'=土曜)
  • NOT IN ('0', '6') → 日曜・土曜以外 = 平日
  • IN ('0', '6') → 日曜・土曜 = 週末

8-2. COUNT + CASE:条件付きカウント

SUM(CASE WHEN 条件 THEN 1 ELSE 0 END) で、条件を満たす行数をカウントできます。

例:カテゴリごとの価格帯別商品数

※横にスクロールできます

SELECT category AS カテゴリ, COUNT(*) AS 総商品数, SUM(CASE WHEN price >= 1000 THEN 1 ELSE 0 END) AS 高価格商品数, SUM(CASE WHEN price >= 500 AND price < 1000 THEN 1 ELSE 0 END) AS 中価格商品数, SUM(CASE WHEN price < 500 THEN 1 ELSE 0 END) AS 低価格商品数 FROM products GROUP BY category ORDER BY 総商品数 DESC;
ポイント:
  • THEN 1 ELSE 0 → 条件に合えば1、合わなければ0
  • SUM(...) → 1を合計 = 条件に合う件数
  • これで「高価格商品が何個あるか」をカウントできる
結果イメージ:
カテゴリ 総商品数 高価格商品数 中価格商品数 低価格商品数
果物 20 5 8 7
野菜 15 2 5 8
肉類 10 8 2 0

8-3. 比率の計算

やりたいこと:週末売上が全体の何%かを計算する

※横にスクロールできます

SELECT strftime('%Y-%m', order_date) AS 年月, SUM(total_amount) AS 総売上, SUM(CASE WHEN strftime('%w', order_date) IN ('0', '6') THEN total_amount ELSE 0 END) AS 週末売上, ROUND( SUM(CASE WHEN strftime('%w', order_date) IN ('0', '6') THEN total_amount ELSE 0 END) * 100.0 / SUM(total_amount), 1 ) AS 週末比率 FROM orders GROUP BY strftime('%Y-%m', order_date) ORDER BY 年月 DESC LIMIT 6;
比率の計算式:

週末比率 = 週末売上 ÷ 総売上 × 100

* 100.0 で小数計算にし、ROUND(..., 1) で小数点1桁に丸めています。

8-4. 例:達成率の計算と評価

やりたいこと:月次売上の目標達成率を計算し、達成状況を評価する

※横にスクロールできます

WITH monthly_sales AS ( SELECT strftime('%Y-%m', order_date) AS year_month, SUM(total_amount) AS actual FROM orders GROUP BY strftime('%Y-%m', order_date) ), targets AS ( SELECT '2024-01' AS year_month, 1000000 AS target UNION ALL SELECT '2024-02', 1200000 UNION ALL SELECT '2024-03', 1500000 UNION ALL SELECT '2024-04', 1800000 ) SELECT t.year_month AS 年月, t.target AS 目標, COALESCE(ms.actual, 0) AS 実績, ROUND(COALESCE(ms.actual, 0) * 100.0 / t.target, 1) AS 達成率, CASE WHEN COALESCE(ms.actual, 0) >= t.target THEN '✅ 達成' WHEN COALESCE(ms.actual, 0) >= t.target * 0.9 THEN '🟡 惜しい' WHEN COALESCE(ms.actual, 0) >= t.target * 0.8 THEN '🟠 要努力' ELSE '❌ 大幅未達' END AS 評価 FROM targets t LEFT JOIN monthly_sales ms ON t.year_month = ms.year_month ORDER BY t.year_month;
結果イメージ:
年月 目標 実績 達成率 評価
2024-01 1,000,000 1,050,000 105.0% ✅ 達成
2024-02 1,200,000 1,100,000 91.7% 🟡 惜しい
2024-03 1,500,000 1,250,000 83.3% 🟠 要努力
2024-04 1,800,000 1,200,000 66.7% ❌ 大幅未達
活用シーン:
  • 営業レポート:売上目標の達成状況
  • KPI管理:指標の達成・未達を自動判定
  • ダッシュボード:視覚的な評価表示

💼 9. 実務での活用パターン

9-1. パターン1:在庫アラートの生成

やりたいこと:在庫状況に応じたアラートメッセージを表示する

※横にスクロールできます

SELECT product_name AS 商品名, stock AS 在庫, CASE WHEN stock = 0 THEN '🔴 緊急: 在庫切れ' WHEN stock <= 5 THEN '🟡 警告: 在庫僅少(' || stock || '個)' WHEN stock <= 20 THEN '🟢 注意: 在庫少なめ' ELSE '✅ 正常' END AS アラート, CASE WHEN stock = 0 THEN '即時発注' WHEN stock <= 5 THEN '至急発注' WHEN stock <= 20 THEN '発注検討' ELSE 'なし' END AS 推奨アクション FROM products ORDER BY stock, product_name;
ポイント:
  • || stock || → 文字列連結で在庫数をメッセージに埋め込み
  • 絵文字を使って視覚的に分かりやすく
  • アラートとアクションを別の列で表示

9-2. パターン2:会員ランクに応じた割引計算

やりたいこと:顧客の総購入額に応じて割引率を変え、割引後価格を計算する

※横にスクロールできます

WITH customer_totals AS ( SELECT customer_id, SUM(total_amount) AS total_purchase FROM orders GROUP BY customer_id ) SELECT c.customer_name AS 顧客名, o.order_id AS 注文ID, o.total_amount AS 通常価格, CASE WHEN ct.total_purchase >= 100000 THEN 'プラチナ(15%OFF)' WHEN ct.total_purchase >= 50000 THEN 'ゴールド(10%OFF)' WHEN ct.total_purchase >= 10000 THEN 'シルバー(5%OFF)' ELSE 'ブロンズ(割引なし)' END AS 会員ランク, ROUND(o.total_amount * (1 - CASE WHEN ct.total_purchase >= 100000 THEN 0.15 WHEN ct.total_purchase >= 50000 THEN 0.10 WHEN ct.total_purchase >= 10000 THEN 0.05 ELSE 0 END)) AS 割引後価格 FROM orders o JOIN customers c ON o.customer_id = c.customer_id JOIN customer_totals ct ON o.customer_id = ct.customer_id ORDER BY o.order_date DESC LIMIT 10;
ポイント:
  • CTEで顧客の総購入額を事前に計算
  • CASE式を2回使用(ランク表示用と計算用)
  • 1 - 割引率 で割引後価格を計算

9-3. パターン3:都道府県別の顧客ランク分布

やりたいこと:都道府県ごとに、顧客ランクの人数分布を横に展開する

コードを段階的に見ていきましょう。

ステップ1:各顧客のランクを判定

※横にスクロールできます

WITH customer_ranks AS ( SELECT c.prefecture, c.customer_id, CASE WHEN SUM(o.total_amount) >= 100000 THEN 'VIP' WHEN SUM(o.total_amount) >= 50000 THEN '優良' ELSE '一般' END AS rank FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.prefecture, c.customer_id )

ステップ2:都道府県ごとにピボット変換

※横にスクロールできます

SELECT prefecture AS 都道府県, COUNT(*) AS 総顧客数, SUM(CASE WHEN rank = 'VIP' THEN 1 ELSE 0 END) AS VIP, SUM(CASE WHEN rank = '優良' THEN 1 ELSE 0 END) AS 優良, SUM(CASE WHEN rank = '一般' THEN 1 ELSE 0 END) AS 一般 FROM customer_ranks GROUP BY prefecture

完成コード:

※横にスクロールできます

WITH customer_ranks AS ( SELECT c.prefecture, c.customer_id, CASE WHEN SUM(o.total_amount) >= 100000 THEN 'VIP' WHEN SUM(o.total_amount) >= 50000 THEN '優良' ELSE '一般' END AS rank FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.prefecture, c.customer_id ) SELECT prefecture AS 都道府県, COUNT(*) AS 総顧客数, SUM(CASE WHEN rank = 'VIP' THEN 1 ELSE 0 END) AS VIP, SUM(CASE WHEN rank = '優良' THEN 1 ELSE 0 END) AS 優良, SUM(CASE WHEN rank = '一般' THEN 1 ELSE 0 END) AS 一般 FROM customer_ranks GROUP BY prefecture ORDER BY 総顧客数 DESC LIMIT 10;
結果イメージ:
都道府県 総顧客数 VIP 優良 一般
東京都 150 20 45 85
大阪府 80 10 25 45
神奈川県 60 8 18 34

📝 練習問題

CASE式をマスターしましょう!

問題 1基本

価格で分類

商品を価格で「高価格」(500円以上)、「低価格」(500円未満)に分類してください。

※横にスクロールできます

SELECT product_name AS 商品名, price AS 価格, CASE WHEN price >= 500 THEN '高価格' ELSE '低価格' END AS 価格帯 FROM products ORDER BY price DESC;

解説:

  • 単純な2分類は WHEN 1つと ELSE で表現
  • 500円ちょうどは「高価格」に分類される

問題 2基本

在庫状況の判定

商品の在庫数で「在庫切れ」(0)、「在庫少」(1-9)、「在庫あり」(10以上)に分類してください。

※横にスクロールできます

SELECT product_name AS 商品名, stock AS 在庫数, CASE WHEN stock = 0 THEN '在庫切れ' WHEN stock < 10 THEN '在庫少' ELSE '在庫あり' END AS 在庫状況 FROM products ORDER BY stock;

解説:

  • 厳しい条件(stock = 0)を先に書く
  • stock < 10 は「1〜9」に当てはまる

問題 3基本

単純CASE式でカテゴリ変換

カテゴリを日本語に変換してください。('fruit'→'果物', 'vegetable'→'野菜', その他→'その他')

※横にスクロールできます

SELECT product_name AS 商品名, CASE category WHEN 'fruit' THEN '果物' WHEN 'vegetable' THEN '野菜' ELSE 'その他' END AS カテゴリ FROM products LIMIT 10;

解説:

  • 単純CASE式は値の変換に便利
  • CASE の直後に列名を書く

問題 4応用

顧客ランクの判定

顧客の総購入額で、10万円以上→「VIP」、5万円以上→「優良」、それ以外→「一般」に分類してください。

※横にスクロールできます

SELECT c.customer_name AS 顧客名, COALESCE(SUM(o.total_amount), 0) AS 総購入額, CASE WHEN SUM(o.total_amount) >= 100000 THEN 'VIP' WHEN SUM(o.total_amount) >= 50000 THEN '優良' ELSE '一般' END AS 顧客ランク FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.customer_name ORDER BY 総購入額 DESC NULLS LAST;

解説:

  • 集計関数(SUM)もCASE式の条件に使える
  • GROUP BY と組み合わせて顧客ごとに判定

問題 5応用

季節の判定

注文日の月から季節を判定してください。(12-2月→冬、3-5月→春、6-8月→夏、9-11月→秋)

※横にスクロールできます

SELECT order_id AS 注文ID, order_date AS 注文日, CASE WHEN strftime('%m', order_date) IN ('12', '01', '02') THEN '冬' WHEN strftime('%m', order_date) IN ('03', '04', '05') THEN '春' WHEN strftime('%m', order_date) IN ('06', '07', '08') THEN '夏' ELSE '秋' END AS 季節 FROM orders ORDER BY order_date DESC LIMIT 10;

解説:

  • INを使って複数の値をまとめて判定
  • strftime('%m', ...)で月を抽出

問題 6応用

条件付きカウント

カテゴリごとに、高価格商品(1000円以上)と低価格商品(1000円未満)の数を表示してください。

※横にスクロールできます

SELECT category AS カテゴリ, COUNT(*) AS 総商品数, SUM(CASE WHEN price >= 1000 THEN 1 ELSE 0 END) AS 高価格商品数, SUM(CASE WHEN price < 1000 THEN 1 ELSE 0 END) AS 低価格商品数 FROM products GROUP BY category ORDER BY 総商品数 DESC;

解説:

  • SUM + CASE で条件付きカウント
  • THEN 1 ELSE 0 で条件に合う行を数える

問題 7応用

平日・週末の売上

月ごとに、平日の売上と週末の売上を別々に集計してください。

※横にスクロールできます

SELECT strftime('%Y-%m', order_date) AS 年月, SUM(CASE WHEN strftime('%w', order_date) NOT IN ('0', '6') THEN total_amount ELSE 0 END) AS 平日売上, SUM(CASE WHEN strftime('%w', order_date) IN ('0', '6') THEN total_amount ELSE 0 END) AS 週末売上, SUM(total_amount) AS 総売上 FROM orders GROUP BY strftime('%Y-%m', order_date) ORDER BY 年月 DESC LIMIT 6;

解説:

  • strftime('%w', ...)で曜日番号(0=日曜、6=土曜)
  • NOT IN ('0', '6') で平日を判定

問題 8チャレンジ

月別売上のピボット

1月から6月までの月次売上を、列に展開して表示してください。

※横にスクロールできます

SELECT strftime('%Y', order_date) AS 年, SUM(CASE WHEN strftime('%m', order_date) = '01' THEN total_amount ELSE 0 END) AS 一月, SUM(CASE WHEN strftime('%m', order_date) = '02' THEN total_amount ELSE 0 END) AS 二月, SUM(CASE WHEN strftime('%m', order_date) = '03' THEN total_amount ELSE 0 END) AS 三月, SUM(CASE WHEN strftime('%m', order_date) = '04' THEN total_amount ELSE 0 END) AS 四月, SUM(CASE WHEN strftime('%m', order_date) = '05' THEN total_amount ELSE 0 END) AS 五月, SUM(CASE WHEN strftime('%m', order_date) = '06' THEN total_amount ELSE 0 END) AS 六月 FROM orders GROUP BY strftime('%Y', order_date) ORDER BY 年;

解説:

  • ピボット変換の基本パターン
  • 各月ごとに SUM + CASE を書く

問題 9チャレンジ

複雑な条件判定

商品に対して、在庫と価格の組み合わせで優先度を判定してください。
- 在庫0 → 「緊急」
- 在庫1-5かつ高額(1000円以上) → 「高」
- 在庫1-9 → 「中」
- その他 → 「低」

※横にスクロールできます

SELECT product_name AS 商品名, price AS 価格, stock AS 在庫, CASE WHEN stock = 0 THEN '緊急' WHEN stock <= 5 AND price >= 1000 THEN '高' WHEN stock < 10 THEN '中' ELSE '低' END AS 優先度 FROM products ORDER BY CASE WHEN stock = 0 THEN 1 WHEN stock <= 5 AND price >= 1000 THEN 2 WHEN stock < 10 THEN 3 ELSE 4 END, product_name;

解説:

  • AND で複数条件を組み合わせ
  • ORDER BY でも CASE を使って優先度順に並べる

問題 10チャレンジ

達成率の評価

月次売上に対して、目標額(150万円)との比較で達成率と評価を表示してください。
- 100%以上 → 「達成」
- 90%以上 → 「惜しい」
- 80%以上 → 「要努力」
- 80%未満 → 「未達」

※横にスクロールできます

WITH monthly_sales AS ( SELECT strftime('%Y-%m', order_date) AS 年月, SUM(total_amount) AS 実績 FROM orders GROUP BY strftime('%Y-%m', order_date) ) SELECT 年月, 実績, 1500000 AS 目標, ROUND(実績 * 100.0 / 1500000, 1) AS 達成率, CASE WHEN 実績 >= 1500000 THEN '達成' WHEN 実績 >= 1350000 THEN '惜しい' WHEN 実績 >= 1200000 THEN '要努力' ELSE '未達' END AS 評価 FROM monthly_sales ORDER BY 年月 DESC LIMIT 6;

解説:

  • CTEで月次売上を事前に計算
  • 達成率と評価を同時に計算

📝 Step 12 のまとめ

✅ 学んだこと
  • CASE式:SQLの条件分岐(if文に相当)
  • 検索CASE式:自由な条件式が書ける(範囲判定など)
  • 単純CASE式:値の変換に便利(= での比較のみ)
  • ピボット変換:行を列に展開(SUM + CASE)
  • 条件付き集計:特定条件だけをカウント・合計
  • ORDER BY での使用:動的な並び替え
📌 CASE式の基本形

※横にスクロールできます

-- 検索CASE式(一般的) CASE WHEN 条件1 THEN 結果1 WHEN 条件2 THEN 結果2 ELSE デフォルト END -- 単純CASE式(値の変換) CASE 列名 WHEN 値1 THEN 結果1 WHEN 値2 THEN 結果2 ELSE デフォルト END -- ピボット変換 SUM(CASE WHEN 条件 THEN 値 ELSE 0 END) AS 列名 -- 条件付きカウント SUM(CASE WHEN 条件 THEN 1 ELSE 0 END) AS 件数
📌 重要なポイント
  • 上から順に評価:厳しい条件を先に書く
  • ENDは必須:忘れるとエラーになる
  • ELSEを推奨:省略するとNULLになる
  • どこでも使える:SELECT、WHERE、ORDER BY、GROUP BY...

❓ よくある質問

Q1: 検索CASEと単純CASEの使い分けは?

値の変換なら単純CASE(コード→名称)、範囲判定や複雑な条件なら検索CASE(>= 1000、AND/OR)を使います。迷ったら検索CASEでOKです。

Q2: ELSEは必須ですか?

必須ではありませんが、推奨します。ELSEがない場合、どの条件にも当てはまらないとNULLになります。想定外の値への対応として、ELSEを書きましょう。

Q3: ピボット変換は自動でできませんか?

一部のDBMS(SQL ServerのPIVOT句など)には専用機能がありますが、SQLiteにはありません。CASE式を使った方法が最も汎用的で、どのDBMSでも使えます。

Q4: CASEはWHERE句でも使えますか?

はい、どこでも使えます。SELECT句、WHERE句、ORDER BY句、GROUP BY句、HAVING句など、どこでも使用可能です。

Q5: CASE式の中にCASE式を入れられますか?

はい、ネストできます。ただし、複雑になりすぎる場合はCTEで分割するか、複数のWHENで書く方が読みやすいです。

Q6: 実務で最もよく使うのは?

ランク判定(顧客、商品)と条件付き集計(カテゴリ別、期間別)が圧倒的に多いです。レポート作成ではピボット変換もよく使います。

🎓 次のステップでは

Step 13: 高度なINSERT/UPDATE/DELETEでは、データの挿入・更新・削除の応用テクニックを学びます。UPSERT、一括更新、サブクエリを使ったデータ操作など、実務で必須のスキルです!

📝

学習メモ

SQL応用・パフォーマンス最適化 - Step 12

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE