COUNT

Count rows or non-NULL values — and know which one you're actually counting.

Edit this pageReport an issue

Syntax

COUNT(*)
COUNT(expression)
COUNT(DISTINCT expression)
ParameterTypeNotes
*literal

Counts every row in the group, NULLs included.

expressionany

Counts rows where the expression is not NULL. Add DISTINCT to count unique non-NULL values instead of every occurrence.

COUNT(*) vs COUNT(column)

SQL playground
Loading editor…
⌘/Ctrl + Enter

COUNT(*) counted every order. COUNT(cancelled_at) only counted the ones where that column isn't NULL — i.e. orders that were actually cancelled. Swapping COUNT(*) for COUNT(some_column) is one of the most common silent bugs in a report: the numbers only match when that column has no NULLs.

Per group

SQL playground
Loading editor…
⌘/Ctrl + Enter

Counting a subset with CASE

COUNT only takes one predicate implicitly (NULL vs not-NULL) — to count rows matching an arbitrary condition, wrap it in CASE and let the implicit NULL rule do the filtering:

SQL playground
Loading editor…
⌘/Ctrl + Enter

No ELSE on purpose — non-matching rows fall through to NULL, which COUNT skips. See Pivot without PIVOT for the full pattern.

Gotchas

  • COUNT(*) and COUNT(column) disagree the moment column can be NULL — decide which one the report actually needs.
  • a WHERE filter changes the row set before COUNT runs; a LEFT JOIN can inflate or misrepresent COUNT(*) if the join fans out (see INNER JOIN: fan-out).
  • COUNT(DISTINCT a, b) (multiple columns) isn't standard — MySQL supports it, Postgres and SQL Server don't; use COUNT(DISTINCT ...) over a concatenated key or a subquery instead.
  • SUM — same NULL-skipping behavior, different aggregate.
  • GROUP BY — the grain COUNT is computed over.
  • Deduplicate rows — using COUNT(*) to find the duplicates first.

Safety checklist

  • decide whether NULLs should count or be excluded
  • confirm the correct grain before grouping
  • check whether join fan-out is inflating the count
Was this page helpful?

On this page