こんにちは、さるまりんです。
SQLでデータの件数を調べたいとき、
SELECT COUNT(*)
FROM users;
のように書くことがあります。
では、こう書いたらどうでしょうか。
SELECT COUNT(nickname)
FROM users;
同じテーブルを数えているので、同じ結果になりそうですよね。
でも、結果が違うことがあります。
その違いに関係しているのが、NULLです。
今回は、COUNT(*)とCOUNT(カラム)で何が変わるのか、小さなデータで見ていきます。
まずは、こんなデータを用意します
今回は、PostgreSQLで試せる例にします。
ユーザーの名前とニックネームが入った、こんなusersテーブルを考えます。
| id | name | nickname |
|---|---|---|
| 1 | さるまりん | salu |
| 2 | オカピ | okapi |
| 3 | ゴリラ | 空文字 |
| 4 | コアラ | NULL |
| 5 | シマウマ | NULL |
ゴリラのニックネームは、文字が一つも入っていない空文字です。''ですね。
コアラとシマウマのニックネームは、NULLになっています。
表では違いがわかるように「空文字」「NULL」と書いています。そういう文字列が保存されているわけではありません。
手元で試す場合は、練習用のデータベースで次のように用意できます。
CREATE TEMP TABLE users (
id integer,
name text,
nickname text
);
INSERT INTO users (id, name, nickname) VALUES
(1, 'さるまりん', 'salu'),
(2, 'オカピ', 'okapi'),
(3, 'ゴリラ', ''),
(4, 'コアラ', NULL),
(5, 'シマウマ', NULL);
TEMP TABLEは一時テーブルです。今回の接続が終わると、自動的に削除されます。
COUNT(*)は行数を数えます
まずは、COUNT(*)です。
SELECT COUNT(*) AS user_count
FROM users;
結果は、こうなります。
user_count
----------
5
テーブルには5行あるので、結果は5です。
ニックネームが入っていても、空文字でも、NULLでも、行として数えます。
COUNT(*)は、
条件に合う行が何行あるか
を数えるものです。
今回はWHEREで条件を指定していないので、テーブルの全行を数えています。
COUNT(カラム)はNULLではない値を数えます
次は、カラム名を指定してみます。
SELECT COUNT(nickname) AS nickname_count
FROM users;
結果は、こうなります。
nickname_count
--------------
3
今度は3になりました。
COUNT(nickname)は、nicknameがNULLではない行を数えます。
今回なら、
saluokapi- 空文字 (
'')
の3件です。
コアラとシマウマのnicknameはNULLなので、数えません。
同じテーブルでも、
COUNT(*)
と、
COUNT(nickname)
では、数えているものが違うんですね。
並べてみると、わかりやすいです。
SELECT
COUNT(*) AS all_count,
COUNT(nickname) AS nickname_count
FROM users;
all_count | nickname_count
----------+---------------
5 | 3
空文字は数えるの?
ここで、少し気になるところです。
ゴリラのニックネームは空文字でした。
何も書いていないのに、数えるのでしょうか。
PostgreSQLでは、空文字とNULLは別のものです。
''
は、長さが0の文字列です。
一方、NULLは、値が不明であることや、値が設定されていないことなどを表します。
そのため、空文字はCOUNT(nickname)でも数えます。
数値の0も同じです。
例えば点数を表すカラムがあった場合、0点はNULLではありません。
COUNT(score)と書けば、0点の行も数えます。
空文字や0を、COUNTが自動的に「値なし」と判断するわけではない
ということですね。
なお、空文字の扱いはデータベースによって違う場合があります。ここではPostgreSQLでの動きとして見ています。
NULLの件数を数えたいときは?
では、ニックネームがNULLの人を数えたいときは、どう書くでしょうか。
まずは、WHEREで対象をしぼるとわかりやすいです。
SELECT COUNT(*) AS null_count
FROM users
WHERE nickname IS NULL;
null_count
----------
2
nickname IS NULLで、ニックネームがNULLの行だけを選び、その行数をCOUNT(*)で数えています。
ここで、
SELECT COUNT(nickname)
FROM users
WHERE nickname IS NULL;
と書くと、結果は0になります。
NULLの行だけを選んでも、COUNT(nickname)はそのNULLを数えないからです。
ちょっとややこしいですが、
「どの行を選ぶか」
と、
「選んだ行の何を数えるか」
を分けると、見えてきます。
全件数と一緒に知りたいなら、引き算でも求められます。
SELECT
COUNT(*) AS all_count,
COUNT(nickname) AS nickname_count,
COUNT(*) - COUNT(nickname) AS null_count
FROM users;
all_count | nickname_count | null_count
----------+----------------+-----------
5 | 3 | 2
全体の行数から、NULLではない件数を引けば、NULLの件数になりますね。
空文字も除いて数えたいときは?
実際には、
「NULLだけではなく、空文字も除いて数えたい」
ということがあるかもしれません。
そんなときは、以前見たNULLIFも使えます。
SELECT COUNT(NULLIF(nickname, '')) AS nickname_count
FROM users;
nickname_count
--------------
2
NULLIF(nickname, '')は、nicknameが空文字ならNULLを返します。
そこにCOUNTを使うので、
- もともと
NULLだったもの - 空文字から
NULLになったもの
の両方が、数える対象から外れます。
残るのは、saluとokapiの2件です。
NULLIFについては、こちらの記事でも紹介しています。
SQLのNULLIFってなに?同じ値ならNULLにしてみよう
なお、半角スペースが入っている文字列は空文字ではありません。今回の書き方では、それは数える対象になります。
「入力されている」と判断する条件は、扱っているデータに合わせて考える必要がありますね。
まとめ
今回は、COUNT(*)とCOUNT(カラム)の違いを見てみました。
| 書き方 | 数えるもの |
|---|---|
COUNT(*) |
行数 |
COUNT(nickname) |
nicknameがNULLではない件数 |
COUNT(*) - COUNT(nickname) |
nicknameがNULLの件数 |
件数を調べるだけなら簡単そうですが、
何を1件として数えたいのか
で、書き方が変わります。
行を数えたいのか。
特定のカラムにNULLではない値が入っている件数を数えたいのか。
空文字も除きたいのか。
ここを少し意識するだけで、「思っていた件数と違う」というときにも確認しやすくなります。
グループごとに件数を数えたい場合は、GROUP BYと組み合わせることもできます。
まずは、COUNT(*)とCOUNT(カラム)で数える対象が違う、というところを覚えておきたいです。
読んでくださってありがとうございました。
それではまた!