SQL / LESSON 13 / SQL13

SQL:CASE式

1学習の目的

2基礎解説

SQL10のヒントで少しだけ使った CASE式を、ここで本格的に扱います。「もし〜なら、こう表示する」という分岐を、SQLの中に組み込めます。

書き方向いている場面
単純CASECASE 列 WHEN '値' THEN …1つの列を値ごとに分ける
検索CASECASE WHEN 条件 THEN …範囲や複数条件で分ける
ELSEELSE '該当なし'省略するとNULLになる
ENDEND AS 別名CASEの終わりを示す
✅ 覚えるべき重要ポイント
  1. CASEは上から順に評価され、最初にマッチした WHEN で確定する。以降の条件は無視される。だから条件は厳しい順・狭い範囲から先に書く(JS04で学んだ考え方と同じ)。
  2. ELSE を省略すると、どの条件にも合わなかった行は NULL になる。「該当なし」と表示したいなら ELSE を明示的に書く。
  3. WHEN price >= 50000 のような条件は、price が NULL だと常に偽になる。結果としてどの条件にも当てはまらず、静かに ELSE に落ちる。「低額」のような紛らわしい分類名がELSEにあると、NULLのデータが誤った区分に混ざる。
  4. CASEはSELECT・WHERE・ORDER BY・GROUP BY のどこでも使える。「PCを最初に、次に周辺機器」のようなカスタムの並び順も、ORDER BYの中にCASEを書けば実現できる。
  5. CASEの中では計算もできるCASE WHEN category='PC' THEN price*0.9 ELSE price END のように、条件によって計算式ごと変えられる。

現場使用例:売上ランク(S/A/B/C)、年齢層の区分、在庫状況の表示(欠品/少/十分)、条件付き割引の計算。「見た目の分類」をデータ本体に持たせず、必要なときにSQLで作るのが基本の考え方。

-- 単純CASE:値ごとの対応
SELECT name,
  CASE category
    WHEN 'PC' THEN '情報機器'
    WHEN '周辺機器' THEN '付属品'
    ELSE '未分類'
  END AS 区分
FROM products;

-- 検索CASE:範囲で分ける
SELECT name, price,
  CASE
    WHEN price >= 50000 THEN '高額'
    WHEN price >= 5000  THEN '中額'
    ELSE '低額'
  END AS 価格帯
FROM products;
-- ⚠ price が NULL の行も「低額」に落ちてしまう

-- NULLを先に弾いておくのが安全
SELECT name, price,
  CASE
    WHEN price IS NULL THEN '価格未定'
    WHEN price >= 50000 THEN '高額'
    WHEN price >= 5000  THEN '中額'
    ELSE '低額'
  END AS 価格帯
FROM products;

-- ORDER BY の中でも使える
SELECT name, category FROM products
ORDER BY CASE category
  WHEN 'PC' THEN 1
  WHEN '周辺機器' THEN 2
  ELSE 3
END;

3基本ドリル(10問)

SQL13-D01 category が 'PC' なら「情報機器」、それ以外は「付属品」と表示するSQLを書け。name と区分(別名「区分」)を取り出すこと。 コーディング★☆☆無料
期待される結果

2列7行。ノートPC 情報機器 / マウス 付属品

ヒント

CASE category WHEN 'PC' THEN '情報機器' ELSE '付属品' END AS 区分 のように書く。

模範解答
SELECT name,
  CASE category
    WHEN 'PC' THEN '情報機器'
    ELSE '付属品'
  END AS 区分
FROM products;
解説

単純CASEの最小形。1つの列の値によって、表示する文字を切り替えている。

SQL13-D02 CASE式で ELSE を省略すると、どの条件にも合わなかった行はどうなるか。
A. エラーになる B. NULLになる C. 空文字になる D. 0になる
選択★☆☆無料
期待される結果

解答 == B

ヒント

「該当する値がない」ときの結果は。

模範解答
B
解説

省略すると暗黙的に ELSE NULL が付くのと同じ。「何も表示されない」ことに気づきにくいので、意図的に省くとき以外はELSEを書く。

SQL13-D03 price が5000円以上なら「高価格」、未満なら「低価格」と表示するSQLを書け(検索CASEを使うこと)。name と区分(別名「区分」)を取り出すこと。price がNULLの行は除いてよい。 コーディング★☆☆無料
期待される結果

6行。ノートPC 高価格 / マウス 低価格

ヒント

CASE WHEN price >= 5000 THEN '高価格' ELSE '低価格' END と書く。単純CASEと違い、列名を最初に書かない。

模範解答
SELECT name,
  CASE
    WHEN price >= 5000 THEN '高価格'
    ELSE '低価格'
  END AS 区分
FROM products
WHERE price IS NOT NULL;
解説

検索CASEは CASE の直後に列名を書かない。WHEN の後ろに好きな条件式を書けるので、範囲による分岐に向いている。

SQL13-D04 CASE式を終わらせるキーワードを補ってコードを完成させよ。 穴埋め★☆☆無料
コード
SELECT name,
  CASE WHEN price >= 10000 THEN '高額' ELSE '通常' ____ AS 区分
FROM products
WHERE price IS NOT NULL;
期待される結果

6行。ノートPC 高額

ヒント

CASEの終わりを示す3文字のキーワード。

模範解答
END
解説

CASE で始まり END で終わる——この対応を忘れると構文エラーになる。

SQL13-D05 CASE WHEN price >= 50000 THEN '高額' WHEN price >= 5000 THEN '中額' ELSE '低額' END で、price が 60000 のときの結果はどれか。
A. 高額 B. 中額 C. 低額 D. 決まらない
選択★☆☆無料
期待される結果

解答 == A

ヒント

上から順に評価され、最初にマッチしたところで確定する。

模範解答
A
解説

60000は2つ目の条件(5000以上)にも当てはまるが、先に書いた1つ目(50000以上)で確定する。以降の条件は評価されない。

SQL13-D06 価格帯を3段階(50000円以上「高額」、5000円以上「中額」、それ未満「低額」)で分類し、price がNULLの商品は「価格未定」とするSQLを書け。name と価格帯(別名「価格帯」)を取り出すこと。 コーディング★★☆無料
期待される結果

7行。サンプル品が 価格未定

ヒント

NULLの判定を一番先に書く。CASE WHEN price IS NULL THEN … の順で並べる。

模範解答
SELECT name,
  CASE
    WHEN price IS NULL THEN '価格未定'
    WHEN price >= 50000 THEN '高額'
    WHEN price >= 5000 THEN '中額'
    ELSE '低額'
  END AS 価格帯
FROM products;
解説

NULLの判定を最初に置くのが安全策。後ろに置くと price >= 50000 などの比較でNULLは常に偽になり、気づかないうちに「低額」に紛れ込んでしまう。

SQL13-D07 カテゴリの表示順を PC → 周辺機器 → その他 にするSQLを完成させよ。 穴埋め★★☆無料
コード
SELECT name, category
FROM products
____ BY CASE category
  WHEN 'PC' THEN 1
  WHEN '周辺機器' THEN 2
  ELSE 3
END;
期待される結果

7行。PCの2件が先頭

ヒント

並べ替えを指示するキーワード。

模範解答
ORDER
解説

CASEで作った数値(1・2・3)を並べ替えの基準にしている。アルファベット順や五十音順ではない、業務の都合に合わせた並び順を作れる。

SQL13-D08 CASE WHEN price >= 50000 THEN '高額' ELSE '低額' END で、price が NULL の行はどう分類されるか。
A. NULLのまま表示される B. エラーになる C. 「低額」に分類される D. 自動的に除外される
選択★★☆無料
期待される結果

解答 == C

ヒント

price >= 50000 の比較結果は、price がNULLだと何になるか。

模範解答
C
解説

これがCASE式最大の落とし穴。NULLとの比較は「不明(偽として扱われる)」になるので、条件に一度もマッチせずELSEに落ちる。ELSEに紛らわしい分類名を置いていると、欠損データが紛れ込んで気づけない。

SQL13-D09 在庫が10個未満なら「在庫少」、10個以上なら「在庫十分」、NULLなら「在庫未定」と分類するSQLを書け。name と状態(別名「状態」)を取り出すこと。 コーディング★★☆無料
期待される結果

7行。新商品Aが在庫未定、サンプル品(在庫3)が在庫少

ヒント

WHEN stock IS NULL を最初に書く。

模範解答
SELECT name,
  CASE
    WHEN stock IS NULL THEN '在庫未定'
    WHEN stock < 10 THEN '在庫少'
    ELSE '在庫十分'
  END AS 状態
FROM products;
解説

3値のうち2値は数値比較、1値はNULL判定という組み合わせ。実務のデータには必ずこの3パターン(正常値・境界値・欠損値)が混在する。

SQL13-D10 category が 'PC' の商品だけ10%引き(price*0.9)、それ以外は定価のままの価格(別名「販売価格」)を、name と並べて取り出すSQLを書け。price がNULLの行は除くこと。 コーディング★★☆無料
期待される結果

6行。ノートPC 115200.0

ヒント

CASE WHEN category='PC' THEN price*0.9 ELSE price END のように、THENの中で計算する。

模範解答
SELECT name,
  CASE
    WHEN category = 'PC' THEN price * 0.9
    ELSE price
  END AS 販売価格
FROM products
WHERE price IS NOT NULL;
解説

THENの中は固定の文字列だけでなく計算式も書ける。条件によって計算方法そのものを変えられるので、割引ロジックの表現に向いている。

4実践シナリオ(5問)

SQL13-S01 【在庫アラート一覧】在庫状況を「⚠在庫切れ間近」(10個未満)・「在庫あり」(10個以上)・「未登録」(NULL)の3段階で分類し、name と状態(別名「状態」)を取り出すSQLを書け。未登録を最優先で判定すること コーディング★★☆無料
期待される結果

7行。新商品Aが未登録、サンプル品(在庫3)が ⚠在庫切れ間近

ヒント

WHEN stock IS NULL を最初に置く。

模範解答
SELECT name,
  CASE
    WHEN stock IS NULL THEN '未登録'
    WHEN stock < 10 THEN '⚠在庫切れ間近'
    ELSE '在庫あり'
  END AS 状態
FROM products;
解説

絵文字も文字列として扱えるので、CASEの中に混ぜて視覚的に目立たせることができる。画面に表示するレポートでは、こういう一工夫が読みやすさを左右する。

SQL13-S02 【顧客ランク付け】顧客ごとの売上合計(LEFT JOINで取引0円も含む)に応じて、20万円以上「ゴールド」、10万円以上「シルバー」、それ未満「ブロンズ」とランク付けするSQLを書け。顧客名(別名「顧客」)とランク(別名「ランク」)を、売上の多い順に取り出すこと。 コーディング★★☆無料
期待される結果

4行。田中商事 ゴールド が先頭

ヒント

LEFT JOINで売上を計算し(SQL10)、その結果にCASEを適用する。売上が0円でも数値なので条件判定できる(NULLではない)。

模範解答
SELECT
  c.name AS 顧客,
  CASE
    WHEN COALESCE(SUM(p.price * o.qty), 0) >= 200000 THEN 'ゴールド'
    WHEN COALESCE(SUM(p.price * o.qty), 0) >= 100000 THEN 'シルバー'
    ELSE 'ブロンズ'
  END AS ランク
FROM customers c
LEFT JOIN orders   o ON c.id = o.customer_id
LEFT JOIN products p ON o.product_id = p.id
GROUP BY c.name
ORDER BY SUM(p.price * o.qty) DESC;
解説

COALESCEで0円に変換してあるので、CASEの条件はNULLを気にせず書ける。SQL10で学んだ「集計時のNULL対策」と、本章の「CASEのNULL対策」が両方効いている。

SQL13-S03 【カスタム並び順】商品を「PC」→「周辺機器」→「未分類」の順に並べ、同じ区分内では価格の高い順にするSQLを書け。name・category・price を取り出すこと。 コーディング★★☆無料
期待される結果

7行。ノートPCが先頭

ヒント

ORDER BY にCASEと通常の列を両方指定できる。カンマで区切る。

模範解答
SELECT name, category, price
FROM products
ORDER BY
  CASE category
    WHEN 'PC' THEN 1
    WHEN '周辺機器' THEN 2
    ELSE 3
  END,
  price DESC;
解説

ORDER BYの中でCASEと通常の列を組み合わせられる。第1基準はカスタム順、第2基準は価格順——JOINやGROUP BYと同じ考え方がここでも使える。

SQL13-S04 【条件付き集計】price を「高額」(5万円以上)「中額」(5千円以上)「低額」「未定」に分類し、区分ごとの商品数(別名「件数」)を取り出すSQLを書け(CASEをGROUP BYで使うこと)。 コーディング★★★無料
期待される結果

4行。未定 1 / 高額 1 / 中額 3 / 低額 2

ヒント

SELECTのCASEに別名を付け、GROUP BYではその別名を使う。

模範解答
SELECT
  CASE
    WHEN price IS NULL THEN '未定'
    WHEN price >= 50000 THEN '高額'
    WHEN price >= 5000 THEN '中額'
    ELSE '低額'
  END AS 区分,
  COUNT(*) AS 件数
FROM products
GROUP BY 区分;
解説

CASEで作った分類そのものを GROUP BY の基準にできる。SQL07で学んだGROUP BYが、データに無い切り口でも使えるようになった。集計の自由度が大きく広がる。

SQL13-S05 【会員種別による割引計算】顧客のエリアによって割引率を変える(東京なら5%引き、大阪なら3%引き、それ以外は割引なし)。顧客ごとの売上合計(LEFT JOIN・0円含む)に割引を適用した金額(別名「請求額」)を、顧客名(別名「顧客」)と並べて取り出すSQLを書け。 コーディング★★★無料
期待される結果

4行。田中商事 282625.0(東京・5%引き)

ヒント

CASE の中で売上合計に掛け算をする。CASE WHEN c.area='東京' THEN 合計*0.95 …のように、area は顧客ごとに1つの値なのでGROUP BYの列にそのまま使える。

模範解答
SELECT
  c.name AS 顧客,
  CASE
    WHEN c.area = '東京' THEN COALESCE(SUM(p.price * o.qty), 0) * 0.95
    WHEN c.area = '大阪' THEN COALESCE(SUM(p.price * o.qty), 0) * 0.97
    ELSE COALESCE(SUM(p.price * o.qty), 0)
  END AS 請求額
FROM customers c
LEFT JOIN orders   o ON c.id = o.customer_id
LEFT JOIN products p ON o.product_id = p.id
GROUP BY c.name, c.area;
解説

集計値そのものに条件付きの計算を適用している。エリアという「顧客の属性」と、売上という「集計結果」を、CASEの中で1つの式にまとめて扱っている。

5仕上げ課題

SQL13-FINAL 【総合顧客ステータスレポート】経営陣から「顧客ごとの取引状況とランクを1枚にまとめてほしい」と依頼された。

表示する列
① 顧客名 → 別名「顧客」
② エリア → 別名「エリア」
③ 売上合計(0円も表示)→ 別名「売上」
④ ランク(20万円以上「ゴールド」/10万円以上「シルバー」/1円以上「ブロンズ」/0円「未取引」)→ 別名「ランク」
⑤ 優先度(未取引なら「要フォロー」、それ以外は「経過観察」)→ 別名「優先度」

並び順:売上の大きい順

期待される結果(5列 × 4行)
顧客 | エリア | 売上 | ランク | 優先度
田中商事 | 東京 | 297500 | ゴールド | 経過観察
佐藤物産 | 東京 | 160000 | シルバー | 経過観察
鈴木工業 | 大阪 | 75000 | ブロンズ | 経過観察
高橋建設 | 福岡 | 0 | 未取引 | 要フォロー
期待される結果

5列 × 4行が完全一致

ヒント

LEFT JOINで顧客ごとの売上を計算する(SQL10)。ランクは「0円」を最初に判定しないと、0が price>=1 のELSEに落ちて「ブロンズ」になってしまう——NULLではなく0という値そのものに対する条件の順序に注意する。優先度は売上=0かどうかで判定する。

模範解答
SELECT
  c.name AS 顧客,
  c.area AS エリア,
  COALESCE(SUM(p.price * o.qty), 0) AS 売上,
  CASE
    WHEN COALESCE(SUM(p.price * o.qty), 0) = 0 THEN '未取引'
    WHEN COALESCE(SUM(p.price * o.qty), 0) >= 200000 THEN 'ゴールド'
    WHEN COALESCE(SUM(p.price * o.qty), 0) >= 100000 THEN 'シルバー'
    ELSE 'ブロンズ'
  END AS ランク,
  CASE
    WHEN COALESCE(SUM(p.price * o.qty), 0) = 0 THEN '要フォロー'
    ELSE '経過観察'
  END AS 優先度
FROM customers c
LEFT JOIN orders   o ON c.id = o.customer_id
LEFT JOIN products p ON o.product_id = p.id
GROUP BY c.name, c.area
ORDER BY 売上 DESC;
解説

この章で学んだNULLの罠は、実は「0」にも形を変えて現れる。もし「未取引」の判定を後回しにしていたら、高橋建設の売上0円はprice >= 200000 にも >= 100000 にも当てはまらず、そのままELSEの「ブロンズ」に落ちていた。0円の顧客が「ブロンズランク」と表示される——一見もっともらしいが、実態は「取引が無い」であって「少ない」ではない。境界のケースを最初に判定するという、この章で繰り返した原則が、NULLだけでなく0のようなありふれた値にも当てはまる。

そして同じ集計式(COALESCE(SUM(…), 0))を3箇所で書いている点にも注目してほしい。売上・ランク・優先度のすべてがこの1つの値に依存しているが、SQLの仕様上、列の別名をCASEの中で再利用することはできない(環境による)。同じ式を繰り返し書くのは冗長に見えるが、確実に動く書き方でもある。次章のウィンドウ関数や、実務ではCTE(WITH句)を使うと、この重複を解消できる。

18章の集大成として、この一枚のレポートにはJOIN・GROUP BY・COALESCE・CASE・ORDER BYが揃っている。SQL01でSELECTだけだったところから、ここまで積み上げてきた。

次章ではテーブルの結合を離れ、いよいよデータを変更する側——INSERT・UPDATE・DELETEに入る。ここまでは「見る」だけだったSQLが、「書き換える」道具に変わる。