SQL:CASE式
1学習の目的
- CASE式で「条件に応じて表示を変える」処理が書けるようになる。価格帯・ランク・区分名など、データそのものには無い分類をSQLの中で作れるようになる。
- CASEの評価順序とNULLの扱いを理解する。条件から漏れたデータが、気づかないうちに誤った分類に落ちるという罠を回避できるようになる。
2基礎解説
SQL10のヒントで少しだけ使った CASE式を、ここで本格的に扱います。「もし〜なら、こう表示する」という分岐を、SQLの中に組み込めます。
| 形 | 書き方 | 向いている場面 |
|---|---|---|
| 単純CASE | CASE 列 WHEN '値' THEN … | 1つの列を値ごとに分ける |
| 検索CASE | CASE WHEN 条件 THEN … | 範囲や複数条件で分ける |
| ELSE | ELSE '該当なし' | 省略するとNULLになる |
| END | END AS 別名 | CASEの終わりを示す |
- CASEは上から順に評価され、最初にマッチした WHEN で確定する。以降の条件は無視される。だから条件は厳しい順・狭い範囲から先に書く(JS04で学んだ考え方と同じ)。
- ELSE を省略すると、どの条件にも合わなかった行は NULL になる。「該当なし」と表示したいなら ELSE を明示的に書く。
- ⚠ WHEN price >= 50000 のような条件は、price が NULL だと常に偽になる。結果としてどの条件にも当てはまらず、静かに ELSE に落ちる。「低額」のような紛らわしい分類名がELSEにあると、NULLのデータが誤った区分に混ざる。
- CASEはSELECT・WHERE・ORDER BY・GROUP BY のどこでも使える。「PCを最初に、次に周辺機器」のようなカスタムの並び順も、ORDER BYの中にCASEを書けば実現できる。
- 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問)
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つの列の値によって、表示する文字を切り替えている。
A. エラーになる B. NULLになる C. 空文字になる D. 0になる 選択★☆☆無料
解答 == B
「該当する値がない」ときの結果は。
B
省略すると暗黙的に ELSE NULL が付くのと同じ。「何も表示されない」ことに気づきにくいので、意図的に省くとき以外はELSEを書く。
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 の後ろに好きな条件式を書けるので、範囲による分岐に向いている。
SELECT name, CASE WHEN price >= 10000 THEN '高額' ELSE '通常' ____ AS 区分 FROM products WHERE price IS NOT NULL;
6行。ノートPC 高額
CASEの終わりを示す3文字のキーワード。
END
CASE で始まり END で終わる——この対応を忘れると構文エラーになる。
A. 高額 B. 中額 C. 低額 D. 決まらない 選択★☆☆無料
解答 == A
上から順に評価され、最初にマッチしたところで確定する。
A
60000は2つ目の条件(5000以上)にも当てはまるが、先に書いた1つ目(50000以上)で確定する。以降の条件は評価されない。
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は常に偽になり、気づかないうちに「低額」に紛れ込んでしまう。
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)を並べ替えの基準にしている。アルファベット順や五十音順ではない、業務の都合に合わせた並び順を作れる。
A. NULLのまま表示される B. エラーになる C. 「低額」に分類される D. 自動的に除外される 選択★★☆無料
解答 == C
price >= 50000 の比較結果は、price がNULLだと何になるか。
C
これがCASE式最大の落とし穴。NULLとの比較は「不明(偽として扱われる)」になるので、条件に一度もマッチせずELSEに落ちる。ELSEに紛らわしい分類名を置いていると、欠損データが紛れ込んで気づけない。
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パターン(正常値・境界値・欠損値)が混在する。
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問)
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の中に混ぜて視覚的に目立たせることができる。画面に表示するレポートでは、こういう一工夫が読みやすさを左右する。
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対策」が両方効いている。
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と同じ考え方がここでも使える。
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が、データに無い切り口でも使えるようになった。集計の自由度が大きく広がる。
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仕上げ課題
表示する列
① 顧客名 → 別名「顧客」
② エリア → 別名「エリア」
③ 売上合計(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が、「書き換える」道具に変わる。