結論からいうと、特定条件に一致する行の総数だけが必要なら、WHERE句で対象行を絞ってCOUNT(*)を実行するのが最も簡単です。たとえば有効な注文を数えるなら、SELECT COUNT(*) FROM orders WHERE status = 'active';という形にします。同じ結果行に複数条件の件数を並べたい場合はCASE式を集計し、分類ごとの件数が必要ならGROUP BY、集計後の件数で分類を絞るならHAVINGを使います。
COUNTの誤集計は、構文そのものよりも、NULL、重複、JOINによる行の増加、期間の端点、WHEREとHAVINGの役割違いから起きやすい問題です。まず「数えたい単位は行か、値か、一意の値か」「条件は集計前か、集計後か」を決めると、使う書き方を迷いません。以下では読み取り専用のSELECTから始め、結果を検証してから既存レポートへ反映する順で説明します。
目的からCOUNTの書き方を選ぶ
- 条件に合う全行を数える:WHEREとCOUNT(*)を使います。NULLを含む行も、一致した行として1行ずつ数えます。
- 特定列に値が入っている行だけ数える:COUNT(列名)を使います。その列がNULLの行は数えません。
- 重複を除いた値の種類数を数える:COUNT(DISTINCT 列名)を使います。NULLは通常、対象に含まれません。
- 複数条件の件数を横並びにする:SUM(CASE WHEN 条件 THEN 1 ELSE 0 END)を条件ごとに用意します。
- 分類ごとの件数を出す:GROUP BYで分類列をまとめ、COUNT(*)を集計します。
- 件数が一定以上の分類だけ残す:GROUP BYの後にHAVING COUNT(*)を指定します。
SQL製品ごとに拡張構文や返り値の型には差があります。たとえばPostgreSQLには集約関数ごとのFILTER句がありますが、すべてのデータベースで同じように使えるとは限りません。移植性を優先するならWHERE、CASE、GROUP BY、HAVINGという基本形から選び、利用中の製品の公式仕様で差分を確認してください。
条件が1つならWHEREとCOUNT(*)で数える
注文テーブルから、状態がactiveの行を数える基本形は次のとおりです。列名や値は実際のスキーマに合わせます。文字列比較では大文字・小文字、末尾空白、照合順序の影響を受ける製品もあるため、想定外の件数なら実データの表記を先に確認します。
SELECT COUNT(*) AS active_count
FROM orders
WHERE status = 'active';
期間も加えるなら、日時列の境界を明示します。日時に時刻が入る場合、終了日を<= '2026-07-31'と書くと、製品や型によっては7月31日の午前0時しか含まれないことがあります。次月の先頭を含めない半開区間にすると、時刻精度に左右されにくくなります。
SELECT COUNT(*) AS july_count
FROM orders
WHERE created_at >= '2026-07-01'
AND created_at < '2026-08-01';
ANDとORを混ぜるときは、意図した単位を丸括弧で囲みます。たとえば「有効、または保留の注文で、かつ7月分」を数えるなら、状態のOR条件を括弧でまとめます。括弧がないと演算子の優先順位によって別の集合を数える可能性があります。
COUNT(*)とCOUNT(列名)の違いを押さえる
COUNT(*)はWHEREを通過した行数を数えます。一方、COUNT(email)はemailがNULLではない行だけを数えます。「会員総数」を求めたいのに任意入力のemail列を指定すると、未入力会員が集計から抜けます。逆に「メール登録済み会員数」が目的ならCOUNT(email)が適切です。MicrosoftのCOUNT公式仕様とPostgreSQLの集約関数公式資料も、行数と非NULL値数を区別しています。
SELECT
COUNT(*) AS all_members,
COUNT(email) AS members_with_email,
COUNT(DISTINCT email) AS unique_emails
FROM members;
COUNT(DISTINCT email)は重複しない非NULL値の数です。人の数とは限りません。家族で同じメールアドレスを使う、表記ゆれがある、複数アカウントが同じ値を持つ、といったデータでは業務上の「人数」と一致しないため、主キーや会員IDなど、数えたい単位を表す列を選びます。
複数条件を1行で数えるならCASEを集計する
状態別の件数を1行に並べたいとき、条件ごとにクエリを実行する必要はありません。各行が条件に合えば1、合わなければ0を返すCASE式をSUMで足します。この形はSQL Server、PostgreSQL、MySQLなどで理解しやすく、条件を見比べやすいのが利点です。
SELECT
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) AS closed_count
FROM orders;
COUNT(CASE WHEN 条件 THEN 1 END)という書き方もあります。条件に合わない行ではCASEがNULLを返し、COUNTがNULLを数えない性質を利用します。ただし、ELSE 0を付けると0も非NULLなので全行を数えてしまいます。レビュー時の読み違いを減らすなら、SUMとELSE 0の組み合わせを採用し、0件時の返り値も実際のデータベースで確認すると堅実です。
条件が重なる場合、各件数の合計は全体件数を超えます。たとえば「高額注文」と「有効注文」は同じ行が両方に入れます。排他的な分類が必要なら、CASEの条件順と境界を明示し、各行が必ず1分類だけに入る設計になっているか、サンプル行で確認します。
分類別はGROUP BY、集計後の条件はHAVINGを使う
部署ごと、状態ごと、月ごとなど、値のグループ単位で件数を返すにはGROUP BYを使います。SELECTに出す非集約列は原則としてGROUP BYにも含めます。MySQLの集約関数公式リファレンスにも、COUNTとGROUP BYの基本が示されています。
SELECT status, COUNT(*) AS order_count
FROM orders
WHERE created_at >= '2026-07-01'
AND created_at < '2026-08-01'
GROUP BY status
ORDER BY status;
WHEREは集計に入れる元の行を絞り、HAVINGはGROUP BYで作った集計結果を絞ります。「7月の注文を状態別に数え、その件数が10以上の状態だけ表示する」なら、期間はWHERE、件数はHAVINGです。SELECT句の論理的な処理順はSQL ServerのSELECT公式資料で確認できます。
SELECT status, COUNT(*) AS order_count
FROM orders
WHERE created_at >= '2026-07-01'
AND created_at < '2026-08-01'
GROUP BY status
HAVING COUNT(*) >= 10;
件数が合わないときは母集団から診断する
- まずCOUNTを外す:同じFROM、JOIN、WHEREで主キーと判定列を少数表示し、何が対象になっているか目視します。
- JOIN前後を比べる:1対多のJOINでは親1件が子の数だけ増えます。親を数えるならCOUNT(DISTINCT 親ID)が要件に合うか検討します。
- NULLを分けて数える:
列 IS NULLと列 IS NOT NULLを別々に数え、欠損が差の原因か確認します。 - 期間の端を確認する:開始直前、開始時刻、終了直前、次期間の開始時刻にあるテスト行で境界を確かめます。
- 文字列の実値を見る:activeとActive、空文字とNULL、前後の空白を同一視してよいかを決めます。
- 同時更新を考慮する:比較する2つのクエリの間にデータが変われば、正しいSQLでも件数は一致しません。同じ時点のスナップショットや検証用データで比べます。
性能問題を疑う前に、正しい集合を返していることを確定します。その後、実行計画を使える環境なら、WHEREやJOINで使う列の探索方法と読み取り量を確認します。COUNT(*)をCOUNT(1)へ機械的に置き換えるだけで速くなると決めつけず、製品、テーブル、索引、統計情報に基づいて判断してください。
安全に検証し、問題があれば元へ戻す
ここで扱った例はSELECTだけなので、それ自体は行を更新しません。それでも本番レポートやアプリのSQLを直接置き換えず、元のSQLと期待件数を保存し、同じ条件で新旧を並べて比較します。総件数、条件別件数、NULL件数、重複除外後の件数を記録し、差が説明できてから採用します。ビューや保存済みクエリを変更する場合は、定義を複製するか履歴を残し、問題時に旧定義へ戻せる状態にします。
よくある質問:0件ならNULLになりますか。COUNTは一致行がなければ通常0を返します。一方、SUM(CASE…)は入力行自体が0件のとき、製品によってNULLになる場合があります。必要なら製品の仕様を確認し、COALESCEで0へ変換します。
よくある質問:WHEREとHAVINGを入れ替えても同じですか。同じとは限りません。WHEREはグループを作る前の行、HAVINGは集計後のグループが対象です。行単位で判定できる条件はWHEREへ置くと、意図が明確になり、集計対象も早い段階で絞れます。
まとめ
条件付きCOUNTの出発点は、WHEREで対象行を定めてCOUNT(*)で数える形です。列の非NULL件数ならCOUNT(列名)、一意値ならCOUNT(DISTINCT 列名)、複数条件を横並びにするならCASEの集計、分類別ならGROUP BY、集計値の絞り込みならHAVINGを選びます。件数が合わないときは、COUNTだけを眺めず、元の行、NULL、重複、JOIN、期間境界を順に確認してください。公式仕様を基準に読み取り専用のSELECTで比較し、旧クエリへ戻せる状態で置き換えるのが確実です。

コメント