野球大会から学ぶ　MySQLのNULLの落とし穴

# この記事で紹介したいこと
* MySQLの比較条件でのNULLの扱われ方
* NULLの扱い方
* SQLを書くときに気をつけていること

# MySQLの比較条件でのNULLの扱われ方

ここは球太くんのチームの野球道具が管理されている倉庫
用具を登録しただけだと、どうやら`stock`カラムの値は`NULL`になるようです。

倉庫のデータ
`baseball_stock`テーブル 
|ID  |name  |stock  |
|---|---|---|
|1  |バット(軟式)  |5  |
|2  |バット(硬式)  |3  |
|3  |ボール(軟式)  |10  |
|4  |ボール(硬式)  |1  |
|5  |グローブ(軟式)|18  |
|6  |グローブ(硬式)|20  |
|7  |ロジン  |NULL  |

球太くん「あ、今日野球観戦に行くんだった！念のため`ボール(硬式)`を持っていこう」
球太くん「サインもらえるといいな〜」

```sql:ボール(硬式)を1個出庫
UPDATE `baseball_stock` SET `stock` = `stock` - 1 WHERE `name` = 'ボール(硬式)';
```

無事に1個出庫されました！
`baseball_stock`テーブル 
|ID  |name  |stock  |
|---|---|---|
|1  |バット(軟式)  |5  |
|2  |バット(硬式)  |3  |
|3  |ボール(軟式)  |10  |
|4  |ボール(硬式)  |**0**  |
|5  |グローブ(軟式)|18  |
|6  |グローブ(硬式)|20  |
|7  |ロジン  |NULL  |

球太くんは無事にサインボールがもらえたようです。
よかったですね！

〜後日〜
球太くん「そうだ、来季の野球大会に向けて、足りない用具を補充しておかなきゃ」

```sql:在庫がない用具はどれかな〜
SELECT * FROM baseball_stock WHERE stock = 0;
```
結果
|ID  |name  |stock  |
|---|---|---|
|4  |ボール(硬式)  |0 |

球太くん「ボール(硬式)だけ補充すればいいのかな。」
球太くん「**念のため他の用具の在庫数も見ておこう！**」

```sql:在庫0以外の用具も見るぞ〜
SELECT * FROM baseball_stock WHERE stock <> 0;
```

結果
|ID  |name  |stock  |
|---|---|---|
|1  |バット(軟式)  |5  |
|2  |バット(硬式)  |3  |
|3  |ボール(軟式)  |10  |
|5  |グローブ(軟式)|18  |
|6  |グローブ(硬式)|20  |

球太くん「**他の用具は補充しなくてもよさそうだな。よし！**」

これで来季も楽しく野球ができますね！
しかしこの後、球太くんに大変なことが起こります。

〜野球大会当日〜
監督「今日はピッチャーのコントロールが悪いな・・・手が滑ってばっかりだ・・・」
監督「あれ、ピッチャーロジン持って無いじゃん」
監督「球太くん、君の作った在庫管理システム化はどうなっているんだい？」
球太くん「確かに**不足している用具**と、**その他の在庫数**も見たんですけどね・・・」
球太くん「**見落としがあったのかもしれません・・・**」

※ロジンはピッチャーが指につける滑り止めです

結果ピッチャーはコントロールが荒れに荒れて、初戦で負けてしまいました。
球速は速かったので、ロジンがあれば勝てていたかもしれませんね。

# 対処法

何故球太くんは
```sql:在庫をしっかり確認するSQL
SELECT * FROM baseball_stock WHERE stock = 0;
SELECT * FROM baseball_stock WHERE stock <> 0;
```
を実行したにもかかわらず、当日ロジンを用意できることができなかったのでしょうか。
それは、ロジンの在庫数の値が`NULL`であることが原因でした。
実は`NULL`は、比較演算子`=`にも`<>`にもヒットしません。

`IS NULL`や`IS NOT NULL`
を使う必要がありました。

球太くんに上記の知識を与えた上で、少し時を戻しましょう。

〜時を戻した後日〜
球太くん「そうだ、来季の野球大会に向けて、足りない用具を補充しておかなきゃ」

```sql:在庫がない用具はどれかな〜
SELECT * FROM baseball_stock WHERE stock = 0;
```
結果
|ID  |name  |stock  |
|---|---|---|
|4  |ボール(硬式)  |0 |

球太くん「**おっといけない！NULLの考慮が漏れていた！**」

```sql:在庫がない(NULL込み)用具はどれかな〜
SELECT * FROM baseball_stock WHERE stock = 0 OR stock IS NULL;
```
結果
|ID  |name  |stock  |
|---|---|---|
|4  |ボール(硬式)  |0 |
|7  |ロジン  |NULL  |

球太くん「ボール(硬式)とロジンを補充・・・っと」

〜野球大会当日〜
監督「今日はピッチャーのコントロールがいいな！ロジンもちゃんと用意されている！」
監督「球太くんの在庫管理は完璧だ！」
球太くん「ありがとうございます！」

無事に野球大会で優勝することができました！
裏方の仕事も楽じゃないですね。


今回球太くんは`IS NULL`を使って、ロジンを確保できました。
記法の話しになりますが、他にもNULLを見つける手立てはあります。

```sql:結果は一緒だよ
SELECT * FROM baseball_stock WHERE stock = 0 OR stock IS NULL;
SELECT * FROM baseball_stock WHERE IFNULL(stock,0) = 0;
SELECT * FROM baseball_stock WHERE stock = 0 OR stock <=> NULL;
```

# SQLを書くときに気をつけていること
`NULL`許可されているカラムの検索では、`NULL`のことを意識しましょう。
まだ`=`の検索は**それだけ**を見つけるには十分なのですが、
`!=`の検索には注意が必要です。
**それ以外**を見つけたいはずなのですが、`NULL`のレコードが検索対象外になってしまいます。
球太くんの例で言うとこちらです。

NULLのレコードが除外されてしまいます。
```sql:0以外の用具の在庫数も見るぞ〜
SELECT * FROM baseball_stock WHERE stock <> 0;
```

結果
|ID  |name  |stock  |
|---|---|---|
|1  |バット(軟式)  |5  |
|2  |バット(硬式)  |3  |
|3  |ボール(軟式)  |10  |
|5  |グローブ(軟式)|18  |
|6  |グローブ(硬式)|20  |

`NULL`のレコードを除外しない書き方
```sql:0以外の用具の在庫数も見るぞ〜
SELECT * FROM baseball_stock WHERE stock <> 0 OR stock IS NULL;
SELECT * FROM baseball_stock WHERE stock <> 0 OR stock <=> NULL;
```

結果
|ID  |name  |stock  |
|---|---|---|
|1  |バット(軟式)  |5  |
|2  |バット(硬式)  |3  |
|3  |ボール(軟式)  |10  |
|5  |グローブ(軟式)|18  |
|6  |グローブ(硬式)|20  |
|7  |ロジン  |NULL  |

**0以外**を見つけたい時でも、`NULL`の事を書いてやらないとダメなんですね。
知っている人からすると簡単な話しですが、知っているのと使えるのはまた別かもしれませんので、
しょーもないところでバグを出して野球大会で優勝を逃すようなことがないよう、慌てず落ち着いてSQL書くようにしましょう！