前回に複数のSQL文の結果をうまく1つのSQL文にまとめる話をしました。今回もその続きです。

有効と無効のそれぞれの商品数

実行して知りたい結果は、有効な商品と無効な商品のそれぞれの商品数です。

SQL文のwhereで条件を指定すれば、

mysql> select count(*) from product where active_flag = 'Y';
+----------+
| count(*) |
+----------+
|     9492 |
+----------+
1 row in set (0.01 sec)

mysql> select count(*) from product where active_flag = 'N';
+----------+
| count(*) |
+----------+
|     4065 |
+----------+
1 row in set (0.01 sec)

と2つのSQL文を実行します。

しかし、これを1つのSQL文にすると、

mysql> select count(active_flag = 'Y') as 有効な商品数, count(active_flag = 'N') as 無効な商品数 from product;
+--------------------+--------------------+
| 有効な商品数       | 無効な商品数       |
+--------------------+--------------------+
|              13557 |              13557 |
+--------------------+--------------------+
1 row in set (0.02 sec)

結果おかしいですね。有効も無効も同じ商品数となってしまいました。しかも両方ともすべての商品数です。

COUNT()関数の引数

SQL文のCOUNT()関数では、その引数の値がNULLでないときにカウントします。

例えば、Laravelのデフォルトのプロジェクトで作成されるDBテーブルのusersに対して、以下のようにクエリーすると、

select count(remember_token) from users

ログインで「次回から自動ログインする」(Remember Me?)のチェックボックスをオンにしてログインしたユーザーのみのレコード数を得られます。なぜなら、remember_tokenはレコード作成時にはNULLの値であり、先のようにログインしたときのみにremember_tokenにランダムの文字列が入ります。

先の、有効も無効も結果が同じ商品数となったケースは、以下のように、引数に指定するExpressionはあるいはの値となり、NULLの値とはなりません。それゆえに、それらの引数でCOUNT()すると全レコードが対象となります。

mysql> select (active_flag = 'Y'), (active_flag = 'N') from product where active_flag = 'Y' limit 1;
+---------------------+---------------------+
| (active_flag = 'Y') | (active_flag = 'N') |
+---------------------+---------------------+
|                   1 |                   0 |
+---------------------+---------------------+
1 row in set (0.00 sec)

ちなみに、引数に*を指定するとき、つまりの馴染みのCOUNT(*)では、レコードにNULLの値が含まれても全レコードを返します。

条件付きでレコードをカウントするには

さて、それではどう条件付きをカウントしたらよいでしょうか?

1つは先のExpressionが返す値を利用して、合計を計算するSUM()の関数を利用できます。

mysql> select sum(active_flag = 'Y') as 有効な商品数, sum(active_flag = 'N') as 無効な商品数 from product;
+--------------------+--------------------+
| 有効な商品数       | 無効な商品数       |
+--------------------+--------------------+
|               9492 |               4065 |
+--------------------+--------------------+
1 row in set (0.02 sec)

あるいは、IF()の関数で返す値を非NULLあるいはNULLとしてからカウントします。

mysql> select count(if(active_flag = 'Y', 1, null)) as 有効な商品数, count(if(active_flag = 'N', 1, null)) as 無効な商品数 from product;
+--------------------+--------------------+
| 有効な商品数       | 無効な商品数       |
+--------------------+--------------------+
|               9492 |               4065 |
+--------------------+--------------------+
1 row in set (0.01 sec)

ごちゃごちゃしているなら、以下のようにシンプルにもできます。

mysql> select count(active_flag = 'Y' or null) as 有効な商品数, count(active_flag = 'N' or null) as 無効な商品数 from product;
+--------------------+--------------------+
| 有効な商品数       | 無効な商品数       |
+--------------------+--------------------+
|               9492 |               4065 |
+--------------------+--------------------+
1 row in set (0.01 sec)

By khino