SQLのCOUNT(*)とCOUNT(カラム)は何が違う?NULLがあると件数が変わる理由

こんにちは、さるまりんです。

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ではない行を数えます。

今回なら、

  • salu
  • okapi
  • 空文字 ('')

の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と組み合わせることもできます。

SQLのGROUP BYとHAVINGについて

まずは、COUNT(*)とCOUNT(カラム)で数える対象が違う、というところを覚えておきたいです。

読んでくださってありがとうございました。

それではまた!