ある日、ダッシュボードを確認していた営業担当から連絡がありました。
「みかんの売上、消えてませんか?」
確認してみると、確かにおかしい。
昨日まで表示されていた売上が、なぜか空欄になっています。
データ連携が失敗したのか。
BIツールの設定がおかしいのか。
それともSQLの不具合か。
調査を始めました。
まずは元データを確認する
売上テーブルを確認します。
| 商品 | 売上数 |
|---|---|
| りんご | 10 |
| みかん | 20 |
ちゃんと入っています。
売上データ自体は問題なさそうです。
JOIN後のデータを確認する
次に、店舗別の売上を結合しているSQLを確認しました。
SELECT
m.product_name,
a.sales_qty,
b.sales_qty,
a.sales_qty + b.sales_qty AS total_qty
FROM product_master m
LEFT JOIN store_a_sales a
ON m.product_id = a.product_id
LEFT JOIN store_b_sales b
ON m.product_id = b.product_id;
結果を見てみると、
| 商品 | 店舗A | 店舗B | 合計 |
|---|---|---|---|
| りんご | 10 | 5 | 15 |
| みかん | 20 | NULL | NULL |
原因はすぐに分かりました。
店舗Bにみかんの売上データが存在していなかったのです。
でも、なぜNULLになるのか
ここで少し混乱しました。
店舗Aでは20個売れている。
なら合計も20になるはずでは?
しかしSQLはそう考えません。
SQLにとってNULLは0ではなく、
「値が存在しない」
を意味します。
そのため、
20 + NULL
の結果は
NULL
になります。
売上は消えたのではありません。
NULLに飲み込まれていたのです。
そこでCOALESCEを知る
調べていると、ある関数にたどり着きました。
COALESCEです。
この関数は、
「NULLだったら代わりの値を返す」
という機能を持っています。
試しにSQLを書き換えてみます。
SELECT
m.product_name,
COALESCE(a.sales_qty, 0)
+ COALESCE(b.sales_qty, 0) AS total_qty
FROM product_master m
LEFT JOIN store_a_sales a
ON m.product_id = a.product_id
LEFT JOIN store_b_sales b
ON m.product_id = b.product_id;
結果はこうなりました。
| 商品 | 合計 |
|---|---|
| りんご | 15 |
| みかん | 20 |
戻った。
売上が帰ってきた。
NULLは敵ではない
このとき学んだのは、
「NULLは悪者ではない」
ということです。
SQLは正しく動いていました。
問題は、
私がNULLの振る舞いを理解していなかったことでした。
ただし、何でも0にしてはいけない
ここでひとつ注意があります。
店舗Bの売上がNULLだった理由は本当に
「売上0件」
だったのでしょうか。
もしかすると、
- データ連携エラー
- 集計処理の失敗
- まだデータ未到着
だったかもしれません。
その場合、
COALESCE(sales_qty, 0)
で0にしてしまうと、問題を隠してしまうことになります。
COALESCEは便利ですが、
NULLを0にしてよいかどうかを判断するのは人間の仕事です。
まとめ
今回のトラブルの原因は、
「売上が消えた」
のではなく、
「LEFT JOINで発生したNULLによって計算結果がNULLになっていた」
ことでした。
そしてその解決策がCOALESCEです。
もしJOIN後の集計結果が突然おかしくなったら、
まずはNULLを疑ってみてください。
あなたの売上も、どこかでNULLに飲み込まれているかもしれません。