この記事は「GoQSystem Advent Calendar 2025」4日目の記事です。
こんにちは、GoQSystemでバックエンドエンジニアをしている品川です。
条件によって取得する値を変えたい時、これまではPHP側で条件分岐を書いていました。 今回、SQL側でも条件分岐ができること、場合によってはその方がパフォーマンスも良くなることを知りました。未来の自分に向けてまとめておきます。
CASE式の基本
SQLで条件分岐をしたい時にとても便利なのがCASE式です。 CASE式は以下2つの書き方ができます。
例1
CASE color WHEN 'white' THEN '白' WHEN 'black' THEN '黒' ELSE NULL END
例2
CASE WHEN color = 'white' THEN '白' WHEN color = 'black' THEN '黒' ELSE NULL END
例1、例2はそれぞれ単純CASE式、検索CASE式と呼ばれ、どちらを使っても良いのですが、単純CASE式は「指定したカラムの値が、特定の値と等しいか(=)」しか判定できないのに対して、検索CASE式は「あらゆる条件式(不等号、複数カラム、NULL判定など)」を書くことができるので、検索CASE式の方を使うのをおすすめします。
ELSE NULLは省略可能で、省略した場合は暗黙的にELSE NULLとして解釈されます。ただ、うっかり書き忘れた時に意図せずNULLを返してしまったり、他の人がコードを読んだときにNULLが返ってくることが一見してわかりづらいので、省略せずに常に書く習慣をつけた方が良いです。
また、CASE式の終わりにENDを書き忘れるのもよくあるミスですが、こちらは省略不可で書き忘れるとエラーになります。注意してください。
CASE式を使った具体例
実際にどのような使い方ができるか、いくつか具体例を紹介します。
年齢によって成人/未成年を判定する
customer_informationテーブル
| id | name | age |
|---|---|---|
| 1 | 佐藤 | 20 |
| 2 | 鈴木 | 16 |
| 3 | 田中 | NULL |
SELECT id, name, CASE WHEN age >= 18 THEN '成人' WHEN age < 18 THEN '未成年' ELSE '不明' END AS age FROM customer_information;
実行結果
| id | name | age |
|---|---|---|
| 1 | 佐藤 | 成人 |
| 2 | 鈴木 | 未成年 |
| 3 | 田中 | 不明 |
都道府県から地域を判定する
addressテーブル
| zip_code | prefecture | city | street_address |
|---|---|---|---|
| 5300001 | 大阪 | 大阪市 | 梅田1丁目 |
| 6000001 | 京都 | 京都市 | 四条通 |
| 7600001 | 香川 | 高松市 | 中央通り |
| 7700001 | 徳島 | 徳島市 | 駅前通り |
| 1000001 | 東京 | 千代田区 | 千代田1丁目 |
SELECT zip_code, CASE WHEN prefecture IN ('大阪', '京都', '兵庫', '奈良') THEN '関西' WHEN prefecture IN ('香川', '徳島', '愛媛', '高松') THEN '四国' ELSE 'その他' END AS region, city, street_address FROM address;
実行結果
| zip_code | region | city | street_address |
|---|---|---|---|
| 5300001 | 関西 | 大阪市 | 梅田1丁目 |
| 6000001 | 関西 | 京都市 | 四条通 |
| 7600001 | 四国 | 高松市 | 中央通り |
| 7700001 | 四国 | 徳島市 | 駅前通り |
| 1000001 | その他 | 千代田区 | 千代田1丁目 |
動物の種類ごとに頭数をカウントする
CASE式は集約関数(SUMやCOUNTなど)の中で使うこともできます。 これを利用すると、行として持っているデータを列に変換して集計する、いわゆる「クロス集計」のようなことがSQLだけで可能になります。
animalsテーブル
| id | species |
|---|---|
| 1 | 犬 |
| 2 | 犬 |
| 3 | 猫 |
| 4 | 犬 |
| 5 | 猫 |
SELECT SUM(CASE WHEN species = '犬' THEN 1 ELSE 0 END) AS number_of_dogs, SUM(CASE WHEN species = '猫' THEN 1 ELSE 0 END) AS number_of_cats FROM animals;
実行結果
| number_of_dogs | number_of_cats |
|---|---|
| 3 | 2 |
まとめ
CASE式を使うことで、SQLの段階で柔軟な条件分岐を行えるようになり、アプリケーション側(GoQSystemではPHPを使用しています)での分岐処理を減らすことができます。
また、大量のレコードを扱う場合は、すべての値を取得してからアプリケーション側でループ処理を行うのに比べて、SQL側の条件分岐により値を整形してから取得する方が、データ転送量の削減や処理速度の向上につながることもあります。(ただし、必ずしも常にSQL側の方が速いわけではありません。条件やインデックス、DB負荷などにより最適解は変わるため、状況に応じて使い分けることが大切です。)
PHPとSQL両方の知識を深めることによって、状況に応じた最適な手法を取ることができます。