ひとり勉強ログ

ITエンジニアの勉強したことメモ

MySQL講座#18 CASE式

CASE式とは

本ページで学ぶCASE式は、「式」という語がついている通り、「1 + 1」や「120 / 4」のような式と同じく一種の演算を行う機能である。その意味で、CASE式は関数の一種でもある。SQLの機能の中で一、二を争う重要な機能のため、ここでしっかり身につける必要がある。

CASE式は、CASE(場合)という名前が示すように、「場合分け」を記述するときに使う。この場合分けのことを、一般的に「条件分岐」とも呼ぶ。

CASE文の構文

CASE式の構文には、「単純CASE式」と「検索CASE式」の2種類がある。ただし、検索CASE式は単純CASE式の機能をすべて含むので、本ページでは検索CASE式だけを取り上げる。単純CASE式を知りたい方は、本ページ末のコラムを参照していただきたい。

ではさっそく、検索CASE式の構文を見てみる。

CASE WHEN <評価式> THEN <式>
WHEN <評価式> THEN <式>
WHEN <評価式> THEN <式>
・・・
ELSE <式>
END

WHEN句の<評価式>とは、「列 = 値」のように、戻り値が真理値(TRUE/FALSE/UNKNOWN)になるような式のことである。=、!=やLIKE、BETWEENといった述語を使って作る式だと考えてもらえれば良い。

CASE式の動作は、最初のWHEN句の<評価式>が評価されることから始まる。「評価」とは、その式の真理値が何か調べることである。その結果、もし真(TRUE)になれば、WHEN句で指定された式が戻されて、CASE式全体が終わる。もし真にならなければ、次のWHEN句の評価に移る。もしこの作業を最後のWHEN句まで繰り返してなお真にならなかった場合は、「ELSE」で指定された式が戻されて終了となる。

なお、CASE式は、名前に「式」とついていることからも分かる通り、これ全体が1つの式を構成している。そして式は、最終的に1つの値に定まるものであるから、CASE式は、SQL文の実行時には、全体が1つの値に変換される。分岐の多いCASE式を使うと何十行にわたって書くことも珍しくないが、その巨大なCASE式全体が、最後には「1」や「'渡辺さん'」のような単純な値になってしまうのである。

CASE式の使い方

では、CASE式を具体的に使ってみる。例えば、いまchojin(超人)テーブルには、正義超人、悪魔超人、完璧超人という3種類の超人分類が格納されている。これを次のような表示に変えて結果を得る方法を考える。

A:正義超人
B:悪魔超人
C:完璧超人

テーブルにあるレコードには、「A:」や「B:」という文字はついていないので、SQLの中でこれをくっつけてやる必要がある。そうすると、文字列結合の関数「CONCAT」を使うのだな、ということが分かるはずである。

残る問題は「A:」「B:」「C:」を正しいレコードに結びつけてやることである。これをCASE式で実現する。

SELECT name,
CASE WHEN category = '正義超人'
THEN CONCAT('A:', category)
WHEN category = '悪魔超人'
THEN CONCAT('B:', category)
WHEN category = '完璧超人'
THEN CONCAT('C:', category)
ELSE NULL
END AS abc_category
FROM chojin;

結果

+--------------------------------+----------------+
| name                           | abc_category   |
+--------------------------------+----------------+
| キン肉マン                     | A:正義超人     |
| テリーマン                     | A:正義超人     |
| ロビンマスク                   | A:正義超人     |
| ウォーズマン                   | A:正義超人     |
| ラーメンマン                   | A:正義超人     |
| ブロッケンJr                 | A:正義超人     |
| ウルフマン                     | A:正義超人     |
| ステカセキング                 | B:悪魔超人     |
| ブラックホール                 | B:悪魔超人     |
| ミスター・カーメン             | B:悪魔超人     |
以下省略

CASE式の6行が、これで1つの列(abc_category)に相当する。超人分類(category)の名前に応じて、3つの分岐をWHEN句によって作っている。最後に「ELSE NULL」としているが、これは「それ以外の場合はNULLを返す」という意味。ELSE句では、WHEN句に指定した条件以外のレコードをどのように扱うかを記述する。NULL以外にも通常の値や式を書くことが可能。ただし現在は、テーブルに含まれる超人分類が3種類だけなので、実質的にELSE句は無くでも同じである。

ELSE句は、省略して書かないことも可能であるが、その場合は自動的に「ELSE NULL」とみなされることになってしまう。しかし、後から読む人が読み落とすことのないよう、明示的にELSE句を書くようにした方が良い。

なお、CASE式の最後の「END」は省略不可能なので、絶対に書き落とさないよう注意が必要である。

CASE式が書ける場所

このCASE式の便利なところは、まさに「式である」という点である。これが何を意味するかというと、式を書ける場所ならどこにでも書ける、ということなのである。それはつまり、「1 + 1」が書ける場所ならどこでも、という意味である。たとえば、CASE式の便利な使い方として、次のようにSELECT文の結果を行列変換する方法が知られている。

+-----------------+-----------------+-------------------+
| sum_power_seigi | sum_power_akuma | sum_power_kanpeki |
+-----------------+-----------------+-------------------+
|             653 |            5830 |             71899 |
+-----------------+-----------------+-------------------+

これは超人分類(category)ごとに超人強度を合計した結果であるが、普通に超人分類の列をGROUP BY句で集約キーとして使っても、結果は「行」として出力されてしまい、列として並べることはできない。

SELECT category,
SUM(power) AS sum_power
FROM chojin
GROUP BY category;

結果

+--------------+-----------+
| category     | sum_power |
+--------------+-----------+
| 完璧超人     |     71899 |
| 悪魔超人     |      5830 |
| 正義超人     |       653 |
+--------------+-----------+

「列」として結果を得るには、以下のようにSUM関数の中でCASE式を使うことで、列を3つ本当に作ってしまえば良い。

SELECT
SUM(CASE WHEN category = '正義超人'
THEN power ELSE 0 END) AS sum_power_seigi,
SUM(CASE WHEN category = '悪魔超人'
THEN power ELSE 0 END) AS sum_power_akuma,
SUM(CASE WHEN category = '完璧超人'
THEN power ELSE 0 END) AS sum_power_kanpeki
FROM chojin;

このCASE式のやっていることは、超人分類(category)が「正義超人」なり「悪魔超人」なりの特定の値と合致した場合には、その超人の超人強度を出力し、そうでない場合はゼロを出力する、ということである。その結果を合計することで、ある特定の超人分類の超人強度の合計値を算出できるようになるわけである。

単純CASE式

CASE式には2種類ある。1つ目が上記で学んだ「検索CASE式」、そして2つ目がそれを簡略化した「単純CASE式」である。

単純CASE式は、検索CASE式に比べると記述が簡単なのであるが、記述できる条件が限定的であるという欠点を持っている。そのため、基本的には検索CASE式を使ってもらえればよいのだが、ここで構文について簡単に学習しておく。

単純CASE式の構文は以下のようになる。

CASE <式>
WHEN <式> THEN <式>
WHEN <式> THEN <式>
WHEN <式> THEN <式>



ELSE <式>
END

最初のWHEN句から評価を始めて、真になるWHEN句が見つかるまで次々にWHEN句を見ていく動作は、検索CASE式と同じである、また、最後まで真になるWHEN句がなかった場合に、ELSE句で指定された式を返す点も変わらない、違いは、最初の「CASE <式>」で、評価対象になる式を決めてしまう点である。

具体的に、検索CASE式と単純CASE式で同じ意味のSQL文を書いてみる。

検索CASE式で書いた場合

SELECT name,
CASE WHEN category = '正義超人'
THEN CONCAT('A:', category)
WHEN category = '悪魔超人'
THEN CONCAT('B:', category)
WHEN category = '完璧超人'
THEN CONCAT('C:', categ\n\nory)
ELSE NULL
END AS abc_category
FROM chojin;

単純CASE式で書いた場合

SELECT name,
CASE category
WHEN '正義超人' THEN CONCAT('A:', category)
WHEN '悪魔超人' THEN CONCAT('B:', category)
WHEN '完璧超人' THEN CONCAT('C:', category)
ELSE NULL
END AS abc_category
FROM chojin;

単純CASE式では、「CASE category」のように、評価したい式(ここでは列そのものであるが)を記述した後は、WHEN句でもう一度「category」を記述する必要がない。その手軽さが利点ではあるが、逆にWHEN句ごとに違う列に対して条件を指定したい場合などは、単純CASE式で記述することはできない。