`COUNT(*)`と`COUNT(列)`の違いは、何を数えるかです。`COUNT(*)`は結果セットの行数を数え、`COUNT(列)`はその列の値が`NULL`ではない行だけを数えます。件数が合わないときは、まず対象列に`NULL`がないかを確認してください。行数を知りたいなら`COUNT(*)`、値が入っている件数を知りたいなら`COUNT(列)`と考えると判断しやすくなります。
まず結論:COUNT(*)は行数、COUNT(列)はNULLでない値を数える
| 書き方 | 数える対象 | NULLの扱い | 向いている用途 |
|---|---|---|---|
| COUNT(*) | 結果セットの全行 | 列がNULLでも行として数える | 総件数・総レコード数 |
| COUNT(列) | 指定列がNULLではない行 | NULLを数えない | 入力済み件数・値あり件数 |
| COUNT(DISTINCT 列) | 指定列の重複しない非NULL値 | NULLを数えず重複もまとめる | ユニークユーザー数・種類数 |
PostgreSQL 18の公式ドキュメントでは、`count(*)`は入力行数、`count(expression)`は式の値がNULLではない入力行数と定義されています。SQL ServerのCOUNTも同じ区別を持ちます。つまり、COUNTの結果差は『行を数えたいのか』『特定の値が存在する行を数えたいのか』で決まります。
5行のデータでCOUNTの違いを確認する
たとえば注文テーブルに5行あり、クーポンコードが2行だけNULLだとします。
| id | customer_id | coupon_code |
|---|---|---|
| 1 | 101 | WELCOME |
| 2 | 102 | NULL |
| 3 | 103 | SPRING |
| 4 | 104 | NULL |
| 5 | 105 | WELCOME |
SELECT
COUNT(*) AS total_rows,
COUNT(coupon_code) AS coupon_entered,
COUNT(DISTINCT coupon_code) AS coupon_types
FROM orders;
| total_rows | coupon_entered | coupon_types |
|---|---|---|
| 5 | 3 | 2 |
`COUNT(*)`は5行すべてを数えます。`COUNT(coupon_code)`はNULLの2行を除いて3件、`COUNT(DISTINCT coupon_code)`はさらに重複する`WELCOME`をまとめるため2種類です。
なぜCOUNT(列)はNULLを数えないのか
`COUNT(列)`は、列名そのものではなく、その列を式として各行で評価した結果を数えます。COUNT(expression)が対象にするのは非NULLの値です。そのため、0や空文字のように値として存在するものは通常カウントされますが、NULLは対象外になります。
用語解説:NULL
値が0や空文字であることではなく、値が存在しない・不明であることを表す特別な状態です。COUNT(列)ではNULLの行が件数に含まれません。
(NULLの比較やIS NULL・COALESCEまで整理したい場合については『SQL NULL完全攻略|IS NULL・COALESCEの使い分けとDB別バグ防止術5選』をご参照ください)
GROUP BYではグループごとに同じルールが適用される
SELECT
customer_id,
COUNT(*) AS order_rows,
COUNT(coupon_code) AS coupon_entered
FROM orders
GROUP BY customer_id;
`GROUP BY`を使っても意味は変わりません。各グループ内の行数は`COUNT(*)`、そのグループ内で特定列に値がある件数は`COUNT(列)`です。集計結果が想定より少ない場合、GROUP BYだけでなくCOUNT対象列のNULLも確認します。
(GROUP BYとWHERE・HAVINGの役割まで確認したい場合については『GROUP BY使い方完全解説|SQLエラー・WHERE HAVINGの違いは?』をご参照ください)
LEFT JOINではCOUNT(*)とCOUNT(右表のID)を使い分ける
LEFT JOINでは、右側に一致する行がなくても左側の行は残ります。そのため『顧客ごとの注文件数』を数えるときに`COUNT(*)`を使うと、注文が0件の顧客にも1行分が残るケースがあります。実際に一致した注文だけを数えたいなら、右表のNULLにならないIDをCOUNTします。
SELECT
c.id,
c.name,
COUNT(o.id) AS order_count
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id
GROUP BY c.id, c.name;
ここで`COUNT(o.id)`は、一致する注文がない行では`o.id`がNULLになるため0件になります。『JOIN後の行数』を数えるのか、『右表で一致した実データ件数』を数えるのかを分けて考えるのがポイントです。
NULLそのものの件数を数える方法
NULLの件数が知りたい場合は、総行数から非NULL件数を引くと確認できます。
SELECT
COUNT(*) AS total_rows,
COUNT(coupon_code) AS non_null_rows,
COUNT(*) - COUNT(coupon_code) AS null_rows
FROM orders;
条件を明示したい場合はCASE式で数える方法もあります。
SELECT
COUNT(CASE WHEN coupon_code IS NULL THEN 1 END) AS null_rows
FROM orders;
COUNTを選ぶときの判断基準
- テーブルや検索結果の全行数を知りたい → `COUNT(*)`
- 特定項目が入力されている件数を知りたい → `COUNT(列)`
- 重複を除いた値の種類数を知りたい → `COUNT(DISTINCT 列)`
- LEFT JOINで右表の一致件数を知りたい → 右表の非NULL保証されたキーを`COUNT`
- NULL件数を知りたい → `COUNT(*) – COUNT(列)`または条件付きCOUNT
COUNTで迷ったときは、SQLの書き方から考えるより『何を1件として数えたいか』を先に言葉にすると選びやすくなります。
(COUNT以外のSQLコマンドも用途別に確認したい場合については『SQLコマンド一覧21選|シーン別で迷わない使い方+チートシート』をご参照ください)
よくある質問
COUNT(列)は0を数えますか?
はい。0はNULLではなく値なので、対象列が0でもCOUNT(列)の件数に含まれます。
COUNT(列)は空文字を数えますか?
空文字がNULLとは別の値として保存されるDBではカウント対象です。ただしDB製品によって空文字の扱いが異なる場合があるため、対象DBの仕様も確認してください。
COUNT(DISTINCT 列)はNULLを1種類として数えますか?
PostgreSQLやSQL ServerのCOUNT(DISTINCT expression)ではNULLはカウント対象外です。非NULLの重複しない値を数えます。
COUNT(1)とCOUNT(*)はどちらを使えばよいですか?
行数を数える意図なら`COUNT(*)`と書くと目的が明確です。`COUNT(1)`も非NULLの定数式を各行で評価するため行数を数える形になりますが、性能差はDB製品・バージョンの実装に依存するため、この記事では速度目的の置き換えは推奨しません。
まとめ:COUNTの違いは「行」か「値」か
- `COUNT(*)`は結果セットの行数を数える
- `COUNT(列)`はその列がNULLではない行だけを数える
- `COUNT(DISTINCT 列)`は非NULLの重複しない値を数える
- GROUP BYでも各グループ内で同じルールが適用される
- LEFT JOINでは一致件数を数える列を意識する
件数が合わないSQLがあれば、まず`COUNT(*)`と`COUNT(対象列)`を同じSELECTで並べ、NULLによる差が出ているか確認してみてください。