MySQLのNULL値の扱い方について

MySQL 8.0

はじめに

以下公式リファレンスには、NULL値について大きく分けてポイントが4点記載されておりますが、公式リファレンスを見ただけではNULL値の扱い方を十分に理解したとはなかなか言えないと思います。

NULL値の取り扱いを誤ると、SQLが期待通りに動作しないことがありますので、
今回はNULL値の扱い方について、いくつか検証してみましたので紹介したいと思います。

https://dev.mysql.com/doc/refman/8.0/ja/null-values.html

NULL値は「データなし」を意味します。 NULL は大文字と小文字のどちらでも記述できます。

NULL値は、数値型での 0 や文字列型での空文字列などの値とは異なります。

LOAD DATA または SELECT … INTO OUTFILE で実行されるテキストファイルのインポートまたはエクスポート操作の場合、NULL は\N 順序で表されます。

ORDER BY でソートする場合、NULL値は、昇順ソートの場合は他の値の前にソートされ、降順ソートの場合は他の値の後にソートされます。

目次

データ準備

本検証は、MySQL 8.0.26およびMySQL8.4.4で検証して問題ありませんでしたので、MySQL8.0および8.4の仕様に差異はないと思います。
今回は検証用に1つのテーブルを作成し、テストデータを投入します。

テーブル構成としては、動物(ペット)の医療記録データとして、
「測定日」、「種類」、「漢字」、「かな」、「体重」を保持しています。

なお動物(ペット)の名前に思い入れのある飼い主は80%と言われていますが、
テストデータでは珍しい名前を設定しています。

実際に試す際は、動物(ペット)の名前に興味のある漫画や映画の登場キャラクターなど、
愛着が持てるデータに書き換えていただくと楽しさが倍増するかもしれませんね!

NULL値を抽出する方法は?

データベースではnullとそれ以外の値で区別されていると同時に、検索対象としては特殊な扱いを受けるように設計されていますので、ケースごとに検証を行いたいと思います。

今回は、weightカラムの値がNULL値になっている行が選択されるかを見たいと思いますので、上手く抽出できると以下の3行が含まれた形で表示されます。

ケース1:等号、等号否定、不等号で抽出できるのか?

まずはよく使う関係比較演算子の等号、等号否定、不等号を用いて、NULL値が抽出されるかを検証してみます。
なお関係比較演算子にも種類が色々ありますので、1SQL内で複数の関係比較演算子を用いて検証しています。

結果は上記の通り、等号、等号否定、不等号ではNULL値を抽出できませんでした・・・。

等号 と 不等号の検証では、NULL値を比較してもエラーにはなりませんでしたので、
SQLの文法としては問題ないが、NULL値と比較しても値は返ってこないよ・・・ということなのでしょう。

なお等号否定(!=,<>)を利用した際に、NULL値が除外されてしまうという点も注意が必要ですね!

ケース2: 論理演算子(NOT演算子)ではどうなのか?

論理演算子の中には、NOT演算子(NOT,!)が存在します。
果たしてNULL値は出力されるのか実験してみましたが、以下のようにNULL値には確固たる信念があるようで、抽出することができません・・・。
NOT演算子(NOT,!)を利用する場合でも、NULL値が除外されてしまうという点は、注意が必要ですね!

ケース3: NULL値を扱うには、やはりあの演算子か・・・?

NULL値を扱うにはやはり「IS NULL」演算子を利用するというのが一般的だと思います。
以下のように実行することで、今度こそNULL値が取得できていることがわかります。

ちなみに公式リファレンスには以下のように記載されています。

・NULL を調べるために、次に示すように IS NULL および IS NOT NULL 演算子を使用します。
・=、<、または <> などの算術比較演算子を使用して NULL をテストすることはできません。

・・・では、
もし「NULL値を扱うには「IS NULL」演算子のみである?」というクイズを提示された場合、「YES/NO」のどちらで答えますか?

実は「<=>」演算子を利用すると、NULL値の比較と等号(=)を用いた比較ができます!

「<=>」演算子は見た目が宇宙船に似ている事から、
「Spaceship Operator」や、「UFO演算子」と呼ばれることもありますが・・・

MySQLの公式のドキュメントでは、「NULL-safe equal」、「NULL-安全等価」演算子と呼ばれています。

さらに細かく記載すると・・・
以下のようにIS UNKNOWNでもNULL値を取得することはできます。

それならば「<=>」演算子と「UNKNOWN」の組み合わせでも比較できるのか?
細かい部分ですが気になったので、実験してみたところ以下のようにエラーになりました。。。

ケース4:NULL値を含めて値を取り出したい場合のテンプレ

以下はweightが「2300以下」もしくはNULL値を抽出するSQLです。
注意点としては、やはりNULL値を考慮することが必要になりますので、「<=>」もしくは「IS NULL」演算子を用いてNULL値を取得する必要があるということです。

このようにNULL値の取り扱い方について知っておく必要はありますが・・・

一方で、設計時にNULLを使用するシチュエーションはなるべく避け、
その上でテーブル定義作成時に値が必須のカラムにはNOT NULL制約を付けたり、
(NULL値以外の)適切なデフォルト値を設定することも対策として有効です。

集合関数とNULL値の関係はどうなのか?

次に集合関数とNULL値の関係性についてみていきます。
具体的には集計対象にNULL値が含まれる場合、どのような結果になるのかを検証していきたいと思います。

ケース1:COUNT関数編

まずは、medical_recordテーブルのレコード数を数えるために、
SELECT句でcount(*)を指定します。以下の通り全てのレコードがカウントできています。

次にNULL値が含まれるweightカラムをcount関数の引数に指定して検証します。
結果はweightがNULL値となっている3レコードが除外され、カウントしているように見えます。

本当にNULL値をカウントしていないのか確認するために、
GROUP BY句でweightを指定して結果を見てみます。

・・・すると以下の通り、NULL値のレコードは0件(=NULL値はカウントされていない)という事実がわかります。

もし全レコード数を見たい場合は、SELECT句でcount(*)を指定するか・・・
count関数の引数にはPKカラムを指定する方が無難です。

そして抽出対象となるデータが0件の場合や該当データがNULL値であった場合ですが、
COUNT関数の場合はどちらも0が返ってきます!

ケース2:MAX関数編

次にMAX関数の例です。
以下の結果だけ見るとMAX関数の場合は最大値が取得できるので、特にNULLの考慮は不要でしょ!
・・・と思うかもしれません。

例えば抽出対象となるデータが0件の場合や該当データがNULL値であった場合、
結果はどちらもNULL値が返ってきます。

アプリケーションとDBを組み合わせてシステムを構築する都合上、
「DBからの問い合わせ結果がNULL値では、アプリケーション側に不都合が・・・」
という場合には、NULL値を0に変換するような処理が必要となります。

そういった場合は以下のように、
IFNULL関数やCOALESCE関数を利用してNULL値を0に変換することをお勧めいたします。

この時、例えばweightのデータが10件あった場合、
10件の中からNULL値を全て0に置き換える必要はないと思いますので・・・

「max(ifnull(weight,0))」と書くよりも、
以下のように「ifnull(max(weight),0) 」という形で書いた方が良いと考えます。

ケース3:MIN関数編

MIN関数の場合はどうかというと・・・
NULL値を除外した最小値が出力されます!

・・・ですので、
NULL値を除外した最小値を求めたい場合は、
以下のようにwhere weight is not nullと記述する必要はありません。

なおNULL値があった場合は0として扱いたいという場合は、
MIN関数 + IFNULL関数やCOALESCE関数を利用して以下の順番で記載する必要があります。

MIN関数 + IFNULL関数やCOALESCE関数の順序を以下のようにしてしまうと、
NULL値を除いた最小値が出力されます・・・。

これはIFNULL関数やCOALESCE関数を使っている意味がないので注意が必要ですね!

ケース4:SUM関数編

まずSUM関数を利用する前の基礎知識として、
四則演算を行う際にNULL値を含めて計算すると以下のようにNULL値になります。
ちなみに文字列結合においてもNULL値を結合するとNULL値になりますのでこちらも要注意です!

実際にSUM関数を利用するとweightの合計値が表示されているように見えますので、
SUM関数ではNULL値を除外して計算しているということが分かります!

しかし、MAX関数と同様に抽出対象となるデータが0件の場合や
該当データがNULL値であった場合、結果はどちらもNULL値が返ってきます。

NULL値を0に変換したい場合は
IFNULL関数やCOALESCE関数を利用して変換することをお勧めいたします。

ケース5:AVG関数編

最後にAVG関数です。
こちらもSUM関数と同様にNULL値を除外して計算しています!

AVG関数でも抽出対象となるデータが0件の場合や
該当データがNULL値であった場合、結果はどちらもNULL値が返ってきます。

もはやテンプレート化していますが、
他の関数と同様にNULL値を0に変換したい場合はIFNULL関数やCOALESCE関数を利用しましょう!

集合関数を検証した結果のまとめ

今回5つの集合関数を検証した結果をまとめると以下のようになります。

・集合関数はNULL値を除外して処理を行っている模様。
・抽出対象となるデータが0件の場合や該当データがNULL値であった場合・・・
 → COUNT関数は 0 を返す。
 → COUNT関数以外は NULL値 を返す。

ちなみに公式リファレンスには、以下のような記載があります。
今回5つの集合関数の説明欄にはNULL値を含んで処理を行うような記載はなかったので、
NULL値は除外して処理を行っているものとみて良いと思います。

特に明記されていないかぎり、集計関数は NULL値を無視します。

こういう内容は、DBの試験問題に出そうですね!
是非覚えておきましょう!!

まとめ

さて皆さんは「水を入れずにご飯を炊いたこと」はありますでしょうか?
炊きあがった時に目にするものは「ご飯」ではなく、「アツアツの米」ができています!

勿論そのままでは美味しくありませんので、お水とお酒を足して再度炊飯器で炊きなおしたり、
ピラフやパエリア、リゾットなど別の料理に作り直す必要があります。

NULL値も取り扱いを間違え、DB操作のトラブルを起こしてリカバリーが発生しないよう、
本ブログの内容が参考になれば幸いです!

スマートスタイルTECHブログについて

スマートスタイルTECHブログでは、日頃MySQLのサポート業務に従事している有資格者で構成された技術サポートチームがMySQLに関する技術情報を発信しています。データベースのお困りごとはお気軽にご相談下さい。

よかったらシェアしてね!
  • URLをコピーしました!
目次