11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SQL】「文」ではなく「式」として理解するCASE式の真価(達人に学ぶSQL徹底指南書 第2版より)

11
Last updated at Posted at 2026-08-21

1.はじめに

「達人に学ぶSQL徹底指南書 第2版」を読み、CASE式に対する認識が大きく変わりました。これまでは、単なる条件分岐の構文として理解していましたが、評価されて値を返す式として理解するようになりました。

この記事のゴールは、CASEを単なる「文」ではなく、評価されてひとつの値を返す「式」として正しく理解することです。

CASEが「式」であることを腹落ちさせれば、これまでアプリケーション側で泥臭く処理していた複雑な集計や、パフォーマンスを劣化させていた冗長なクエリを、SQL単体でシンプルかつ高速に記述できるようになります。この記事では、実務ですぐに使えるテクニックを交えながら、SQLらしい「集合指向」の思考法へ切り替えるための第一歩を解説します。

2. 最大のパラダイムシフト:「文」ではなく「式」である

SQLにおいて CASE を学ぶ際、最初にぶつかる壁であり、最大のブレイクスルーとなるのがCASEは「文」ではなく「式」であるという事実の理解です。
現場ではよく「CASE文」と呼ばれがちですが、SQL標準における正式名称は「CASE式(CASE Expression)」です。この名前の違いは、単なる言葉遊びではなく、SQLという言語の思想を理解する上で決定的な意味を持ちます。

「文」と「式」の決定的な違い
手続き型言語(Java, Python, Cなど)におけるif 〜 elseは「文」です。
文とは、プログラムの実行フローを制御するための「命令」であり、それ自体が値を持つわけではありません。

一方、SQLの CASE は「式」です。
式とは、評価された結果として「ひとつの値」を返すものです。手続き型言語で言えば、三項演算子( 条件 ? 値1 : 値2 )に似ています。

つまり、SQLエンジンにとってCASE WHEN ... THEN ... ENDというまとまりは、評価が終われば1や'A'といった「単なる一つの値(スカラ値)」と同義になります。

なぜこれが重要なのか?
「CASEが式である」という事実の最大のメリットは、「値(定数や列名)が書ける場所なら、SQLの構文上のどこにでも書ける」という点です。

手続き型言語のif文は、SQLでいうSELECTやWHEREの中に直接埋め込むことはできませんが、CASE式であればそれが可能です。これにより、SQLの表現力は爆発的に向上します。

  • SELECT句で使う(値の変換)
    最も基本的な使い方です。列の値を評価し、別の値に変換して出力します。
SELECT
  user_name,
  CASE WHEN age >= 20 THEN '成人'
       ELSE '未成年' END AS age_category
FROM users;
  • GROUP BY句で使う(任意のカテゴリで集約)
    テーブルに存在しない独自のカテゴリをその場で作成し、そのカテゴリ単位で集計を行うことができます。
-- 年齢層(20代、30代...)ごとにユーザー数をカウント
SELECT
  CASE WHEN age < 20 THEN '20歳未満'
       WHEN age BETWEEN 20 AND 29 THEN '20代'
       WHEN age BETWEEN 30 AND 39 THEN '30代'
       ELSE '40歳以上' END AS age_group,
  COUNT(*) AS user_count
FROM users
GROUP BY
  CASE WHEN age < 20 THEN '20歳未満'
       WHEN age BETWEEN 20 AND 29 THEN '20代'
       WHEN age BETWEEN 30 AND 39 THEN '30代'
       ELSE '40歳以上' END;
  • ORDER BY句で使う(独自のソート順を定義)
    昇順・降順だけでなく、「特定のステータスを一番上に持ってくる」といった業務要件によくある特殊なソートも、CASE式で解決できます。
-- statusが 'URGENT'(緊急) のものを最優先で並べ、その後は登録日順
SELECT task_name, status, created_at
FROM tasks
ORDER BY
  CASE WHEN status = 'URGENT' THEN 0
       ELSE 1 END,
  created_at ASC;
  • UPDATE文のSET句で使う(条件分岐アップデート)
    複数回のUPDATE文を発行しなくても、条件に応じた値の更新を1回のクエリで安全に実行できます。
-- 役職に応じて給与のベースアップ額を変える
UPDATE employees
SET salary = CASE WHEN role = 'Manager' THEN salary * 1.1
                  WHEN role = 'Member'  THEN salary * 1.05
                  ELSE salary END;

このように、「CASEは式である」と認識を改めるだけで、これまで「SQLだけでは無理だから、アプリケーション側(プログラム側)でループして処理しよう」と諦めていた多くの操作が、SQL単体でシンプルに記述できるようになります。

この章で「CASE式がどこにでも書ける」という強力な前提を共有することで、次の章の「クロス集計」や「UNIONの排除」といった応用テクニックの理解が格段にスムーズになります。

3. 実践テクニック①:条件を分岐させた集計(クロス集計)

CASE式のメリットを最も実感しやすいのが、この「クロス集計(行列変換)」です。
業務システムでは「縦持ち(行持ち)のデータを、横持ち(列持ち)のレポート形式に変換してほしい」という要件が頻繁に発生します。

課題:縦持ち(行持ち)データを横持ち(列持ち)の表に変換したい
例えば、次のような都道府県別の人口推移データ(Populationテーブル)があるとします。

-- 縦持ちの元データ
SELECT pref_name, sex, population FROM Population;
pref_name sex population
徳島 1 60
徳島 2 40
香川 1 100
香川 2 100
愛媛 1 100
愛媛 2 50
高知 1 100
高知 2 100

(sex 1:男性、2:女性)

このデータを、以下のような「都道府県ごとの男女別人口」という横持ちのクロス集計表として出力したいとします。

pref_name count_male (男) count_female (女)
徳島 60 40
香川 100 100
愛媛 100 50
高知 100 100

解決策:集約関数の中にCASE式を組み込む
手続き型言語の思考に引きずられていると、一度全データをSELECTして、アプリケーション側のプログラムでループを回して集計しようと考えがちです。しかし、CASE式を使えばSQLだけで完結します。

ポイントは、SUM などの集約関数の引数としてCASE式を渡すことです。前章で学んだ通り、CASEは「式(評価されて値になるもの)」なので、関数の引数として全く問題なく機能します。

CASE式を使ったクロス集計クエリ

SELECT
  pref_name,
  -- 男性の人口だけを合計する
  SUM(CASE WHEN sex = '1' THEN population ELSE 0 END) AS count_male,
  -- 女性の人口だけを合計する
  SUM(CASE WHEN sex = '2' THEN population ELSE 0 END) AS count_female
FROM
  Population
GROUP BY
  pref_name;

どう動いているのか?(SQLエンジンの思考)
このクエリが内部でどう評価されているか、順を追って見てみましょう。

  1. GROUP BY pref_name の実行
    まず、都道府県ごとにレコードがグループ化されます(例えば「徳島」のグループには、男の行と女の行の2行が含まれます)。

  2. SUM 関数内の CASE 式の評価
    徳島グループに対して`count_male列を計算します。
    1行目(男): sex = '1' が真なので、population(60)を返す。
    2行目(女): sex = '1' が偽なので、0 を返す。

  3. SUM による集約
    評価された値が SUM(60 + 0)となり、結果として「60」が出力されます。

これが、SQLで条件付き集計を行う際の強力なデザインパターンです。

4. 実践テクニック②:悪手「UNION」からの脱却(パフォーマンス改善)

CASE式をマスターすることで得られるもう一つの巨大な恩恵が、SQLのパフォーマンス改善です。特に、手続き型言語に慣れたエンジニアが陥りがちな「無用なUNION」を排除できる点は、実務において非常に重要です。

課題:条件によって処理を分けたい時の「UNION病」
例えば、商品のデータ(Itemsテーブル)があり、キャンペーンのために以下の条件で割引価格を算出したいとします。

  • カテゴリが「食品」なら10%オフ
  • カテゴリが「家電」なら20%オフ

手続き型言語の「IF条件がAなら処理A、Bなら処理B」という思考を引きずっていると、SQLでも「条件AのSELECT文」と「条件BのSELECT文」を書き、それを UNION(または UNION ALL)でくっつける、という発想になりがちです。

(悪手)UNION ALLを使ったクエリ

-- 食品の割引価格を計算
SELECT item_name, price * 0.9 AS campaign_price
FROM Items
WHERE category = '食品'

UNION ALL

-- 家電の割引価格を計算
SELECT item_name, price * 0.8 AS campaign_price
FROM Items
WHERE category = '家電';

このクエリは結果としては正しい値を返します。
しかし、パフォーマンスの観点からは非常に効率が悪い悪手です。

なぜUNIONで条件分岐をしてはいけないのか?
RDBMSにおいて、最も重い処理になりやすいのがテーブルへのアクセスです。
上記のUNIONを使ったクエリはItemsテーブルに対するスキャンが2回発生します。条件が3つ、4つと増えてUNIONを繋げれば、スキャン回数も3回、4回と倍増していきます。数百万行、数千万行の大規模なテーブルでこれを行うと、致命的なパフォーマンス低下を引き起こします。

解決策:CASE式で「1回のスキャン」にまとめる
ここで「CASEは式である」という原則を思い出してください。条件分岐はSELECT句の中で「値」として処理すれば良いのです。
(最適解)CASE式を使ったクエリ

SELECT
  item_name,
  CASE category
    WHEN '食品' THEN price * 0.9
    WHEN '家電' THEN price * 0.8
  END AS campaign_price
FROM
  Items
WHERE
  category IN ('食品', '家電');

この書き方であれば、Items テーブルへのスキャンは1回だけで済みます。
1回レコードを読み込み、そのレコードの category の値に応じて、SELECT句の中のCASE式が動的に計算ルールを切り替えてくれます。I/OコストがUNIONの時の半分になるため、劇的なパフォーマンス改善が見込めます。

SQLの思考法:分割して結合するのではなく、集合を一括処理する
UNIONは本来、「異なるテーブルの集合」や「インデックスの効き方が全く違う複雑な検索結果」を結合するためのものです。「同じテーブルに対して、条件によって出力する値を変えたい」という要件に対してUNIONを使うのは、SQLの設計思想に反しています。

  • 手続き型の思考:条件ごとにデータを分割(SELECT)し、後で結合(UNION)する
  • SQL(宣言型)の思考:対象となるデータの集合を一度だけ取得(スキャン)し、要素ごとに評価(CASE)する

この思考の切り替えができるようになると、クエリの実行速度は飛躍的に向上します。

5. CASE式を書く際の「3つの鉄則」

CASE式は強力な反面、書き方を誤ると予期せぬバグの温床になります。実務で安全に運用するための3つの鉄則を紹介します。

1.ELSEは省略しない

CASE式において ELSE 句は文法上省略可能ですが、実務においては絶対に省略してはいけません。

ELSE を省略した場合、どの WHEN 条件にも合致しなかったレコードには暗黙的にNULLが返されます。この暗黙のNULLが、意図せぬ集計エラーや、アプリケーション側でのNullPointerExceptionを引き起こす原因になります。

-- 【悪手】ELSEを省略している
CASE WHEN age >= 20 THEN '成人' END

-- 【正解】明示的にELSEを書く(該当なしをNULLとしたい場合でも明記する)
CASE WHEN age >= 20 THEN '成人' ELSE '未成年' END
CASE WHEN age >= 20 THEN '成人' ELSE NULL END

2.ENDを忘れない
初学者が最もよく遭遇する構文エラーがENDの書き忘れです。
手続き型言語のswitchやifのようなブロック終端記号の感覚に慣れていると、最後にENDをつけるのを忘れがちです。

-- 【エラーになる】ENDが足りない
SELECT
  CASE WHEN sex = '1' THEN '男'
       WHEN sex = '2' THEN '女'
       ELSE '不明'
  AS sex_name -- ここで構文エラー
FROM users;

CASE式の記述を始める際は、まず CASE と END をセットで書いてから中身の WHEN を埋めていく癖をつけることをおすすめします。

3.「単純CASE式」より「検索CASE式」を使う
CASE式には2つの書き方があります。「単純CASE式」と「検索CASE式」です。

-- 1. 単純CASE式(対象の列をCASEの直後に書く)
CASE sex
  WHEN '1' THEN '男'
  WHEN '2' THEN '女'
  ELSE '不明'
END

-- 2. 検索CASE式(WHENの後に条件式を書く)
CASE
  WHEN sex = '1' THEN '男'
  WHEN sex = '2' THEN '女'
  ELSE '不明'
END

結論から言うと、実務では「検索CASE式」に統一すべきです。
単純CASE式は =(等値比較)しか表現できず、以下のような柔軟な条件指定ができません。

  • age >= 20 のような不等号
  • name LIKE '%太郎%' のような部分一致
  • A = 1 AND B = 2 のような複数列の組み合わせ
  • col IS NULL のようなNULL判定(単純CASE式で WHEN NULL と書いても正しく判定されません)

検索CASE式であればこれらすべてに対応可能です。可読性と将来の仕様変更への強さを考慮し、最初から検索CASE式で書くことを習慣づけましょう。

6. おわりに

「達人に学ぶSQL徹底指南書 2版」の第1章で語られているように、CASE式を「式」として理解し活用することは、SQLにおけるパラダイムシフトです。

  • クロス集計による柔軟なデータ変換
  • UNION排除によるパフォーマンスの劇的な改善

これらを使いこなすことで、アプリケーション側のコードを減らし、データベースエンジンが持つ本来のパワーを引き出すことができます。「手続き型(ループと分岐)」の思考から「宣言型(集合と評価)」の思考へ切り替える第一歩として、CASE式は非常に優れた教材だと思います。

これまでは、CASE式を単なる条件分岐の構文として理解していたので、本書は非常に腑に落ちる内容となりました。本記事が、皆さんのSQL力向上のきっかけになれば幸いです。

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?