MySQL道普請便り

第194回COALESCE関数を使ってNULLを返さないようにする

皆さんはNULLの扱いで困ったことは無いでしょうか? NULLは何も無いことを示すフラグのようなもので、実体が無いため単純に比較することができないため、IS NULLやIS NOT NULLを使って対応する必要がありちょっぴり面倒です。またSUMやAVG等で予期せずNULLが帰ってくるパターンなどもあるため少し厄介です。

そんな時に便利に扱えるのが、今回紹介するCOALESCE関数です。

検証環境

今回はdockerで建てたMySQLを使用します。以下のコマンドでdockerを建ててローカルからアクセスをします。

% docker run --platform linux/x86_64 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:latest

アクセス方法は以下の通りになります。

% mysql -uroot -pmy-secret-pw -h127.0.0.1 -P3307

執筆時点では、以下の通りMySQL 8.0.31を使用しています。

mysql>  select version();
+-----------+
| version() |
+-----------+
| 8.0.31    |
+-----------+
1 row in set (0.01 sec)

今回はtestデータベースとdatasテーブルを作成します。

mysql> create test;
mysql> CREATE TABLE `datas` (c1 int, c2 real, c3 varchar(2));

COALESCE関数を使ってみる

さて、COALESCE関数を早速使ってみましょう。使い方は簡単です。関数の引数として値の候補をリストとして渡します。まずはわかりやすく直値を入れて確かめてみましょう。

mysql> SELECT COALESCE(NULL);
+----------------+
| COALESCE(NULL) |
+----------------+
|           NULL |
+----------------+
1 row in set (0.01 sec)

NULLだけを入れた場合には上記のようにNULLが返ってきます。続いて、NULLと1を入れた場合どういう挙動になるか確認してみましょう。

mysql> SELECT COALESCE(NULL,1);
+------------------+
| COALESCE(NULL,1) |
+------------------+
|                1 |
+------------------+
1 row in set (0.01 sec)

1が返ってきました。続いて、その後ろに2を追加したらどうなるか試してみましょう。

mysql> SELECT COALESCE(NULL,1,2);
+--------------------+
| COALESCE(NULL,1,2) |
+--------------------+
|                  1 |
+--------------------+
1 row in set (0.00 sec)

この場合も1が返りました。このように先頭から確認していって、最初にNULL以外のものを発見した場合に返すという関数になります。

もちろん直値以外のカラムでも使用することができます。

COALESCE関数が役に立つ場合

さて、COALESCE関数ですが、うちはNOT NULL制約を適切に付けているからNULLは基本的には出ないので問題ないよ、と思っているかもしれませんが、いくつか例外があると思います。

そのうちの1つですが、AVGやSUM関数などは、中身が空、もしくは全ての行がNULLの場合、NULLを返してきます。

本当かどうか確認してみましょう。

mysql> SELECT SUM(c1) FROM datas;
+---------+
| SUM(c1) |
+---------+
|    NULL |
+---------+
1 row in set (0.01 sec)

まず中身が無いことが確認できました。続いて、実際に結果がどうなるのか試してみましょう。

mysql> SELECT SUM(c1), AVG(c1), COUNT(c1) FROM datas;
mysql> SELECT SUM(c1), AVG(c1), COUNT(c1) FROM datas;
+---------+---------+-----------+
| SUM(c1) | AVG(c1) | COUNT(c1) |
+---------+---------+-----------+
|    NULL |    NULL |         0 |
+---------+---------+-----------+
1 row in set (0.02 sec)

このようにSUMとAVGはNULLに、COUNTは0件が返りました。COUNTは少し特殊なので、同じ感覚で空のテーブルにSUMやAVGを実行すると、思わぬ所でNULLが飛び出してくるなんてこともあります。

ここで特に注意したいのが、NULLと演算を行うとNULLになってしまうという特性です。

mysql> SELECT SUM(c1) + 1 FROM datas;
+-------------+
| SUM(c1) + 1 |
+-------------+
|        NULL |
+-------------+
1 row in set (0.00 sec)

別のテーブルで計算した値と今回のSUM(c1)を、万が一混ぜて計算して出力しようとした時に、結果としてNULLが出力されることになります。SQLで集計などを取る場合は、値が入っていない状態でも取れるように、COALESCE関数を使って初期値を入れてあげましょう。

mysql> SELECT COALESCE(SUM(c1),0) + 1 FROM datas;
+-------------------------+
| COALESCE(SUM(c1),0) + 1 |
+-------------------------+
|                       1 |
+-------------------------+
1 row in set (0.00 sec)

このように、計算できていることがわかります。ちなみに、今回のように別な型を持つカラムを渡すこともできます。これを確認するために、以下のような値をdatasテーブルに入れます。

mysql> INSERT INTO datas (c1, c2, c3) VALUES (1,NULL,NULL),(NULL,2.0,NULL),(NULL,NULL,'3');

一旦SELECTしてみましょう。

mysql> select * FROM datas;
+------+------+------+
| c1   | c2   | c3   |
+------+------+------+
|    1 | NULL | NULL |
| NULL |    2 | NULL |
| NULL | NULL | 3    |
+------+------+------+
3 rows in set (0.01 sec)

1行ごとに各カラムに値が入っていることがわかります。続けて、以下のようなクエリを実行してみましょう。

mysql> SELECT COALESCE(c1, c2, c3) FROM datas;
+----------------------+
| COALESCE(c1, c2, c3) |
+----------------------+
| 1                    |
| 2                    |
| 3                    |
+----------------------+
3 rows in set (0.01 sec)

このように、型の異なるカラムでも使用することができます。

IFNULL関数との違い

COALESCE関数と似た機能にIFNULL関数があります。こちらでも、先ほどと同様のクエリを実行してみましょう。

mysql> SELECT IFNULL(SUM(c1),0) + 1 FROM datas;
+-----------------------+
| IFNULL(SUM(c1),0) + 1 |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.01 sec)

COALESCE関数と同様に動いていることがわかります。では、完全に同じ機能でただのエイリアスなのか?と思われた方もいるかも知れませんが、COALESCE関数は引数にリストを持って評価できるという話をしましたが、IFNULL関数で表現したい場合は、ネストしてあげる必要があります。

また、COALESCE関数はSQL標準ですが、IFNULL関数はMySQL固有のものとなるため、使用を控えたほうが移行などの際に便利かもしれません。

複数の型を入れた場合の挙動

COALESCE関数やIFNULLを使用した場合、戻り値の型がどうなるか考えてみましょう。

MySQLの公式のドキュメントを確認すると、STRING, REAL, INTEGERの順に評価され、なるべく多くの値が入る最大公約数を選ぶようです。文字列が含まれている場合はSTRINGが選択され、実数が含まれている場合はREALが、整数値のみの場合はINTEGERといった具合に選択されます。こちらCREATE TABLE文を使用して確かめてみましょう。

mysql> CREATE TABLE tmp SELECT COALESCE(1,2.0,'test') AS test;
Query OK, 1 row affected (0.07 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> desc tmp;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| test  | varchar(4) | NO   |     |         |       |
+-------+------------+------+-----+---------+-------+
1 row in set (0.03 sec)

STRING, REAL, INTEGERが存在する場合はSTRINGが選択されていることがわかります。続いて、REALとINTEGERの値にした場合はどうなるでしょうか。

mysql> CREATE TABLE tmp2 SELECT COALESCE(1,2.0) AS test;
Query OK, 1 row affected (0.06 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> desc tmp2;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| test  | decimal(2,1) | NO   |     | 0.0     |       |
+-------+--------------+------+-----+---------+-------+
1 row in set (0.03 sec)

decimal型、つまりはREAL型として登録されていることがわかります。続いて、INTEGERのみを指定した場合どうなるでしょうか。

mysql> CREATE TABLE tmp3 SELECT COALESCE(1) AS test;
Query OK, 1 row affected (0.06 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> desc tmp3;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| test  | int  | NO   |     | 0       |       |
+-------+------+------+-----+---------+-------+
1 row in set (0.02 sec)

というようにSTRING, REAL, INTEGERの順に優先されていることがわかりました。戻り値の型が何になるのか直感的にわかりにくいため、別の型のカラムを混ぜないほうが混乱を招かずにすむと思います。特に文字列型と混ぜると、直感に反する動作を起こすと思いますので注意しましょう。

まとめ

今回は、NULL値の扱いが楽になるCOALESCE関数を紹介しました。リストの先頭から評価してNULL以外の値があったらその値を返すという仕組みのため、最後に固定値を入れることができなければ、結局NULLとなってしまうことには注意しましょう。

また、基本的にNULLは厄介なためできる限り正規化を正しく行い、正しく立ち向かいましょう。そのうえで残るものやOUTER JOINした場合、今回例示したようにSUMやAVGなど、仕組み上どうしてもNULLが発生する可能性があるものに、必要に応じてCOALESCE関数を使用しましょう。

プログラムで対応できる場合もあるとは思いますが、こういった関数を知っておくと、SQLだけで対応できることも増えてくると思いますので、知識として覚えておくと良いかもしれません。

おすすめ記事

記事・ニュース一覧