COUNT(*) counts input rows, COUNT(expression) counts rows with a non-NULL expression, and COUNT(DISTINCT expression) counts different known values.
A count sounds simple until a report mixes sales, customers, unknown amounts and repeated purchases. Choose the entity and the NULL rule before choosing which COUNT form to use. Otherwise an accurate calculation can still carry a misleading label.
This reference adds detail to the main course. You can read it independently: the small database below supplies every table and row used in the examples. Start with the question each query answers, then compare the returned values with the data rather than memorising a line of syntax.
Set up the practice database
Run this setup once in a new practice database, before the examples. It uses four customers and six sales. Ana has two sales, Ben has one, Cara has two, and Dev has none.
Sale 106 has no customer link; sale 104 has an unknown amount. These deliberate test cases expose missing information and unmatched relationships.
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name VARCHAR(40) NOT NULL,
city VARCHAR(40),
manager_id INTEGER
);
CREATE TABLE sales (
id INTEGER PRIMARY KEY,
customer_id INTEGER,
amount DECIMAL(10,2),
status VARCHAR(20) NOT NULL,
created_on DATE NOT NULL
);
INSERT INTO customers (id,name,city,manager_id) VALUES
(1,'Ana','Leeds',NULL),(2,'Ben','Leeds',1),
(3,'Cara','Porto',1),(4,'Dev',NULL,2);
INSERT INTO sales (id,customer_id,amount,status,created_on) VALUES
(101,1,10,'paid','2026-01-01'),
(102,1,30,'pending','2026-01-02'),
(103,2,20,'paid','2026-01-02'),
(104,3,NULL,'pending','2026-02-01'),
(105,3,20,'paid','2026-02-02'),
(106,NULL,5,'paid','2026-02-03');An amount of zero would be a known value. The NULL amount here means the value is unknown. A customer with no matching sale differs from a sale whose customer_id is NULL.
The primary keys identify individual records even when amounts or cities repeat. Dates use year-month-day format. The examples specify sorting whenever order matters; insertion order alone does not guarantee display order.
How COUNT() works
COUNT(*) counts every row in its input, including a row whose optional fields are all missing. The sales table has six rows, so its row count is six. COUNT(amount) counts the rows where amount is present, giving five. It does not add the amounts, and it does not ignore a known zero. The difference between those two counts reveals one row needing an amount, provided both counts use the same input and filters.
COUNT(DISTINCT amount) counts distinct known numeric amounts. The known values are five, ten, twenty and thirty, so the answer is four even though five sale rows have known amounts. COUNT(DISTINCT customer_id) gives three identified customers with sales. Neither figure includes the anonymous customer link. If you need to count distinct combinations of several fields portably, select those combinations with DISTINCT in a derived table and count its rows.
Empty groups and zero counts
An aggregate COUNT without GROUP BY returns a summary row containing zero when the filtered input is empty. With GROUP BY, absent groups do not appear unless you first build a complete set of entities and join data to it. To list every customer with a sale count, start from customers and LEFT JOIN sales. Then count the joined sales.id, not COUNT(*), because the outer join creates a placeholder row for a customer with no sale. Dev’s true sale count should be zero, not one.
Counts after joins measure the joined rows. Joining sales to another one-to-many table can multiply each sale and inflate COUNT(*). DISTINCT can sometimes express the intended entity count, but inspect the relationships before using it as a patch. Include a clear grain in the output name, such as sale_count or customer_count. For very large datasets, the result type and potential overflow vary between engines; SQL Server offers COUNT_BIG when its regular integer COUNT is insufficient.
MySQL, PostgreSQL, SQL Server and SQLite
The three single-expression forms are supported by all four engines. Syntax for counting distinct multiple columns differs, so use a distinct subquery for portability. SQL Server COUNT returns int and COUNT_BIG returns bigint. Do not assume a row count can be answered from table metadata without evaluating relevant visibility and filter rules.
Check the engine-specific syntax and version requirements in MySQL COUNT aggregate.
Practice task and worked solution
Return every customer and the number of matching sale records, including Dev with zero.
Try the task before reading its worked solution. The supplied rows make the expected answer checkable by hand.
SELECT c.id,c.name,COUNT(s.id) AS sale_count
FROM customers AS c
LEFT JOIN sales AS s ON s.customer_id=c.id
GROUP BY c.id,c.name ORDER BY c.id;id | name | sale_count
1 | Ana | 2
2 | Ben | 1
3 | Cara | 2
4 | Dev | 0The left join keeps each customer. Counting the sale primary key counts only actual matches, including Cara’s sale with an unknown amount. Grouping by customer id and name supplies one summary per customer, and the unlinked sale belongs to no customer group.
Compare COUNT(), COUNT(amount) and COUNT(DISTINCT amount) after filtering to paid status: expect four, four and three. Then left join every customer and compare COUNT() with COUNT(s.id) for Dev, observing one versus zero. These controlled examples distinguish record population, known-value presence, repeated measures and the outer-join placeholder without changing source data.
SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT Syntax
COUNT()
SELECT COUNT(*) AS sale_rows,
COUNT(amount) AS known_amounts,
COUNT(DISTINCT amount) AS different_amounts
FROM sales;SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT Summary
COUNT(*)counts rows;COUNT(column)counts rows where that column isn'tNULL.COUNT(DISTINCT column)counts different non-NULLvalues.- Without
GROUP BY,COUNTalways returns one row, with 0 for an empty input. - With
GROUP BY, groups with no rows don't appear; start from the full list of entities andLEFT JOINto show zeros. - After a
LEFT JOIN, count a column from the right table, such asCOUNT(s.id), so unmatched rows count as 0.
SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT Examples
Each example shows the code, the output it produces and what each part does.
Rows, known amounts and distinct amounts
Six saved records exist. Five have amounts because sale 104 is missing its amount. The two twenties count as two known records but one distinct amount, so there are four different known amounts.
SELECT COUNT(*) AS sale_rows,
COUNT(amount) AS known_amounts,
COUNT(DISTINCT amount) AS different_amounts
FROM sales;sale_rows | known_amounts | different_amounts
6 | 5 | 4Common SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT Mistakes
COUNT(*) after a LEFT JOIN
COUNT(*) after a customer LEFT JOIN reports one for Dev because it counts the preserved placeholder row. COUNT(s.id) reports zero because that placeholder has no sale id. Counting s.amount would also skip a genuine sale with an unknown amount, which is not correct if the report counts transactions.
SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT in Real Projects
Counts in a daily reconciliation report
A daily reconciliation report should show total records, records with known amounts and distinct identified purchasers separately. These counts serve different checks. A difference between the first two can trigger a data-quality task without changing how many transactions the business actually recorded.
Define counts by entity: transactions, present measurements and distinct purchasers are different metrics. Include a join-free baseline when reconciling a complex joined report. If a new detail join increases the transaction count without new transactions being added, investigate row multiplication before applying DISTINCT or changing the displayed label.
SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT FAQ
Does COUNT(column) skip zero?
No. It skips NULL, not zero or an empty string, because 0 and '' are known values. To count rows with a positive amount, filter or use a condition: COUNT(CASE WHEN amount > 0 THEN 1 END).
Is COUNT(1) a count of ones?
No. The non-NULL constant is present on every input row, so COUNT(1) has the same row-count meaning as COUNT(*); it does not mean the table's first column. Databases execute both the same way, and COUNT(*) is the clearer standard form.
Does COUNT DISTINCT include NULL?
No. For a single expression, it counts different non-NULL values, so the NULL amount in sale 104 isn't counted. If "unknown" should count as one extra category, add it separately: COUNT(DISTINCT amount) + MAX(CASE WHEN amount IS NULL THEN 1 ELSE 0 END).
How do I count rows that match a condition?
Filter with WHERE when it's the only count you need: SELECT COUNT(*) FROM sales WHERE status = 'paid'. To show several conditional counts side by side, use COUNT(CASE WHEN status = 'paid' THEN 1 END) or SUM(CASE ... THEN 1 ELSE 0 END); PostgreSQL and SQLite also support COUNT(*) FILTER (WHERE status = 'paid').
Why is COUNT(*) slow on a large table?
Some databases, including PostgreSQL and MySQL's InnoDB, don't store an exact row count, because different transactions can see different rows. COUNT(*) must therefore scan the table or an index. For dashboards, an approximate count from the database's statistics, or a maintained summary, is often good enough.
SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
What are COUNT(*), COUNT(amount) and COUNT(DISTINCT amount), respectively?
SQL COUNT: COUNT(*), COUNT(Column) and COUNT DISTINCT Quiz
2 questions to check what you learned.
How many rows does COUNT(amount) count here?
- Six, because COUNT(amount) counts every sale record
- Four, because COUNT(amount) removes repeated numeric amounts
- 5
Which count correctly gives Dev zero sales after a LEFT JOIN?
- COUNT(s.id)
- COUNT(*), because the preserved placeholder is a sale
- COUNT(s.amount), because an unknown amount means no sale exists