SQLのCASE WHENで区分ごとに集計する方法|複数条件・ELSE・NULLで件数がずれる原因4つ

SQLのCASE WHENで売上を大口・中口・小口の区分に分け、区分ごとの件数と合計を集計し、最小・最大で確かめる流れを表したイメージ 実務で使うデータ・AIスキル
CASE WHENで区分ごとに集計する 補足: 順番・ELSE・AND/OR・NULLでずれないために

売上を金額で「大口・中口・小口」に分けて、件数と合計を出したい。SQLのCASE WHENで書いてみたら、大口が1件も出てこない。区分名が空の行が混ざる。条件を2つにしたら、件数が多すぎる。

最初に確認したいのは、区分ごとの件数と、金額の最小・最大です。

この記事でわかること

  • CASE WHENで区分を作り、GROUP BY・SUMで集計する書き方
  • 件数・合計がずれる4つの原因と直し方
  • 区分の条件をSQLに残すと、毎月の集計の何が良くなるか

結論:CASE WHENで区分を作り、件数と最小・最大で確かめる

区分ごとの集計は、CASE WHENで1行ずつ区分を付け、GROUP BYかSUMでまとめます。

CASE WHENは、条件に応じて値を返す式です。上から順に条件を見て、最初に当たった値を返します。どの条件にも当たらない行は、ELSEがなければNULL(値なし)になります。

条件の順番や範囲を間違えても、エラーは出ません。区分の数字だけがずれます。そのため、集計したら次の2つを確かめます。

  • 区分の件数を足すと、元の表の件数と合うか
  • 区分ごとの最小・最大が、条件の範囲に収まっているか

まずここから確認:例の売上表に、1行ずつ区分を付ける

集計の前に1行ずつ区分を付けて見ると、CASE WHENがどの行をどの区分に入れたかが分かります。

この記事では、架空の売上表(sales)を例にします。1行が1件の注文で、amountは金額(円)です。

order_idchannelamount
1店舗12000
2EC5000
3EC15000
4店舗800
5電話3000

channelは、注文を受けた窓口です。全部で5件、合計35,800円です。

金額が1万円以上を「大口」、3千円以上を「中口」とし、それ以外を「小口」にします。この記事のSQLは、PostgreSQLとMySQL 8.0以降を想定しています。

SELECT
  order_id,
  amount,
  CASE
    WHEN amount >= 10000 THEN '大口'
    WHEN amount >= 3000 THEN '中口'
    ELSE '小口'
  END AS band
FROM sales;

書き方の骨組みは、次のとおりです。

  • WHEN 条件 THEN 値:条件に当たったら、その値を返す
  • ELSE 値:どの条件にも当たらないときの値
  • END AS 名前:CASEの終わりと、作る列の名前

実行結果

order_idamountband
112000大口
25000中口
315000大口
4800小口
53000中口

この結果では、5000円の注文が1つ目の条件に当たらず、2つ目の条件で「中口」になっています。

2つ目の条件に「1万円未満」を書かなくてよいのは、上から順に見るからです。1つ目に当たった行は、2つ目の条件まで進みません。

条件を上から順に判定していく形が、地味に好きな書き方です。

区分ごとに集計する:GROUP BYで縦に、SUMで横に並べる

区分を行に並べるならGROUP BY、列に並べるならSUMの中にCASE WHENを書きます。

GROUP BYで、区分ごとの件数と合計を出す

区分を付けるSQLをWITH句でまとめ、外側でGROUP BYします。WITH句は、SQLの中で一時的な表に名前をつける書き方です。

WITH classified AS (
  SELECT
    amount,
    CASE
      WHEN amount >= 10000 THEN '大口'
      WHEN amount >= 3000 THEN '中口'
      ELSE '小口'
    END AS band
  FROM sales
)
SELECT
  band,
  COUNT(*) AS orders,
  SUM(amount) AS total_amount
FROM classified
GROUP BY band;

実行結果

bandorderstotal_amount
大口227000
中口28000
小口1800

件数を足すと5件、合計は35,800円で、元の表と合います。

並び順は、ORDER BYを付けないと決まりません。

WITH句を使うのは、区分の条件を1か所に書くためです。GROUP BYに同じCASE WHENをもう一度書く方法もあります。SELECTで付けた名前(band)をGROUP BYで使えるかは、DBによって違います。

SUMの中にCASE WHENを書いて、窓口ごとの売上を横に並べる

窓口ごとの売上を1行に並べるときは、SUMの中にCASE WHENを書きます。

SELECT
  SUM(CASE WHEN channel = '店舗'
      THEN amount ELSE 0 END) AS store_sales,
  SUM(CASE WHEN channel = 'EC'
      THEN amount ELSE 0 END) AS ec_sales,
  SUM(CASE WHEN channel = '電話'
      THEN amount ELSE 0 END) AS phone_sales
FROM sales;

実行結果

store_salesec_salesphone_sales
12800200003000

3つを足すと35,800円で、元の表と合います。

窓口に当たる行はamountを、当たらない行は0を足しています。件数にしたいときは、THEN amount を THEN 1 にします。店舗なら2件です。

件数を数えるときは、COUNTとELSE 0を組み合わせない。COUNTは、NULL以外の値を全部数えます。0もNULLではないので、次の式は全件の5になります。

COUNT(CASE WHEN channel = '店舗' THEN 1 ELSE 0 END)

COUNTを使うなら、ELSEを書かずに、当たらない行をNULLにします。

区分を確かめる:件数と最小・最大を出す

区分ごとの最小・最大を出すと、条件の範囲からはみ出した行に気づけます。GROUP BYのSQLの外側のSELECTに、MIN(amount) と MAX(amount) を足します。

例の表では、大口は12,000〜15,000円です。中口は3,000〜5,000円、小口は800円で、どれも条件の範囲に収まっています。

件数・合計がずれる原因4つ:順番・ELSE・ANDとOR・NULL

CASE WHENの数字がずれるときは、条件の順番・ELSE・ANDとOR・NULLの4つに分けて探します。

起きていること疑う原因最初に見る場所
大口が出ない・中口の最大が1万円以上1. 条件の順番WHENの並び
区分名が空(NULL)の行が出る2. ELSEがないCASEの最後
条件を足した区分に、条件外の行が入る3. ANDとORWHENの中のかっこ
値が空の行が、別の区分に入る4. NULLの判定= NULL の有無

1〜3は、件数と最小・最大を出すと見つかります。4は、条件に使う列に空の値があるかを先に数えます。

原因1:広い条件を上に書いている

問題と原因:CASE WHENは、最初に当たった条件の値を返し、残りの条件は見ません。広い条件(3千円以上)を上に書くと、1万円以上の行もそこで止まります。

CASE
  WHEN amount >= 3000 THEN '中口'
  WHEN amount >= 10000 THEN '大口'
  ELSE '小口'
END AS band

区分ごとの件数と合計は、次のようになります。

bandorderstotal_amount
中口435000
小口1800

この結果では、大口の2件が中口に入っています。件数の合計は5件のままなので、件数を足すだけでは気づけません。

確認方法:区分ごとの最小・最大を出します。中口の最大が15,000円になり、1万円以上の行が入っていると分かります。

どう直すか:狭い条件(1万円以上)から順に書きます。上の条件に当たらなかった行だけが、次の条件に進みます。

原因2:ELSEを書いていない

問題と原因:どのWHENにも当たらない行は、ELSEがないとNULLになります。小口をELSEで付けるつもりで書き忘れると、800円の注文が区分から漏れます。

CASE
  WHEN amount >= 10000 THEN '大口'
  WHEN amount >= 3000 THEN '中口'
END AS band

区分ごとの件数と合計は、次のようになります。

bandorderstotal_amount
大口227000
中口28000
NULL1800

GROUP BYでは、NULLの行も1つの区分としてまとまります。

報告書(会議や上司に出す集計表)に大口と中口だけを写すと、1件・800円が抜けます。

確認方法:GROUP BYの結果に、区分名が空(NULL)の行がないかを見ます。

どう直すか:CASEの最後にELSEを書き、どの条件にも当たらない行の行き先を決めます。小口のように意味のある区分か、「その他」にします。

原因3:ANDとORを混ぜて、かっこを付けていない

問題と原因:WHENの中では、ANDやORで2つ以上の条件をつなげます。ANDとORを混ぜると、ANDが先に計算されます。

例として、店舗と電話の注文のうち、1万円以上を「店舗・電話の大口」にします。

CASE
  WHEN channel = '店舗' OR channel = '電話'
    AND amount >= 10000
    THEN '店舗・電話の大口'
  ELSE 'その他'
END AS band

区分ごとの件数と合計は、次のようになります。

bandorderstotal_amount
店舗・電話の大口212800
その他323000

この条件は「店舗の注文すべて」または「電話の1万円以上」と読まれ、800円の店舗の注文が大口に入っています。

確認方法:区分ごとの最小・最大を出します。店舗・電話の大口の最小が、800円になります。

どう直すか:ORでつないだ部分を、かっこでくくります。

WHEN (channel = '店舗' OR channel = '電話') AND amount >= 10000

直すと、店舗・電話の大口は1件・12,000円になります。

私もANDとORのかっこがずれて、件数が合わなかったりエラーが出たりしたことがあります。

条件が2つの列にまたがるときは、CASEの中にCASEを書く(入れ子)より、ANDでつなげば1段で書けます。

原因4:空(NULL)の判定を「=」で書いている

問題と原因:NULLとの「=」の比較は、真にも偽にもなりません。そのため WHEN amount = NULL は、どの行にも当たりません。

amount >= 10000 などの条件にも当たらないので、金額が空の注文はELSEの「小口」に入ります。

私も、NULLの値が入っていて、計算が合わないかもと思ったことがあります。

確認方法:条件に使う列に、空の値が何件あるかを数えます。COUNT(*) - COUNT(amount) で、amountが空の件数が出ます。例の表は空の値がないので、0です。

どう直すか:IS NULL で判定し、1つ目のWHENに書きます。空の行の行き先がすぐ分かります。

WHEN amount IS NULL THEN '未入力'

それでも合わないとき:条件に使った列の値を一覧にする

4つを確かめても合わないときは、条件に使った列に、どんな値が入っているかを一覧にします。

列の値ごとに件数を出します。

SELECT channel, COUNT(*) FROM sales GROUP BY channel;

「EC」と「EC」が別の行に出たら、表記が違います。

全角と半角や余分な空白は、DBや設定によっては別の値として扱われます。channel = 'EC' に当たらない行は、ELSEの区分に入ります。

表記の違いは、channel IN ('EC', 'EC') のように両方を書くか、元のデータを1つの書き方にそろえます。

区分が正しくても、報告書の数字と合わないなら、集計の対象がずれていることがあります。確かめ方は、次の記事にまとめています。

業務への着地:区分の境目をSQLに残し、毎月同じ基準で数える

区分の条件をCASE WHENに書いておくと、境目がはっきりし、毎月同じ基準で数えられます。

区分を作るときに迷いやすいのは、境目の扱いです。1万円ちょうどを「以上」で大口に入れるのか、「未満」で中口に入れるのか。月ごとの集計なら、どの日付を基準に数えるのか。

私も区分を作るときは、以上・以下・未満や日付の基準を、よく人と確認します。

CASE WHENなら、amount >= 10000 のように、確認した基準がそのまま条件として残ります。1万円ちょうどは大口だと、誰が見ても分かります。

比べると、次のとおりです。

場面BeforeAfter
境目の扱い担当者の解釈で決まる確認した基準が条件に残る
翌月の集計前月の基準を思い出す同じSQLを実行し直す
報告書に写す前区分の合計だけを見る件数と最小・最大を確かめる

基準を人と確認してSQLに残すと、担当が変わっても、同じデータから同じ数字が出ます。

まとめ

CASE WHENで区分ごとに集計するときは、次の5つを確認します。

  1. 狭い条件から順にWHENを書いたか
  2. CASEの最後にELSEを書いたか
  3. ANDとORを混ぜるときに、かっこを付けたか
  4. 空の値を IS NULL で判定したか
  5. 件数の合計と、区分ごとの最小・最大を確かめたか

区分を横に並べるときは、SUMの中にCASE WHENを書き、ELSE 0を付けます。件数を数えるときは、COUNTとELSE 0を組み合わせません。

私は本で勉強したSQLを実務で使うことをくり返しています。この記事で提示した書き方も、ぜひ実務で試してみてください。

またSQLを基本から順番に学ぶなら、次の記事にまとめています。

次に読む記事

どこからが「SQLできる」か、基準と確かめ方をまとめています。

SQLの次にBI・Pythonへどう進むかは、学習ロードマップにまとめています。

タイトルとURLをコピーしました