Data AnalyticsSQL foundations for reliable analysis

COUNT star versus COUNT column in a customer report

PK
Pankit Kumar
Sr. Data Scientist at Parexel (a Goldman Sachs–backed company) · 20 September 2026 · 4 min read
Technically reviewed by Ishaan Sharma
In this article (6 sections)

COUNT(*) counts rows. COUNT(column) counts rows where that expression is not NULL. COUNT(DISTINCT column) counts different non-NULL values. The correct choice follows from what you want to count: records, observed attributes or unique entities.

The distinction becomes important in customer reporting because one customer can have several orders, missing contact details and multiple item lines within an order. Those are different quantities, even when they appear in the same joined table.

Use the commerce SQL lab to run the examples. It contains seven known customers and ten order headers. The records are synthetic and intentionally include missing values and an unmatched customer identifier.

Count customers and contact completeness separately

sql
SELECT
    COUNT(*) AS customer_records,
    COUNT(email) AS records_with_email
FROM customers;

The result is seven customer records and five non-NULL emails. Subtracting the second count from the first shows two missing email values.

This does not prove that five addresses are deliverable, unique or consented for communication. It establishes only that the expression is non-NULL. An empty string is also non-NULL, so a real ingestion process must define whether blank and whitespace-only values are normalized to NULL.

The fixture already uses NULL for missing emails. If you change that convention, update the quality check rather than expecting COUNT to understand the difference between a usable address and a present string. The engine's aggregate reference describes the precise counting behaviour. SQLite aggregate functions.

Count orders without calling them customers

sql
SELECT
    COUNT(*) AS completed_orders,
    COUNT(DISTINCT customer_id) AS customer_ids_on_completed_orders
FROM orders
WHERE status = 'completed';

There are eight completed orders and six distinct customer identifiers on those orders. Two customers ordered twice, so the counts cannot be interpreted interchangeably.

One of the six identifiers is C999, whose customer dimension record is missing. Therefore, “six verified customers” would overstate what the query establishes. A precise label is “six distinct customer IDs on completed orders, including one unmatched ID.” The quality issue should be investigated separately.

This is a useful habit: name a measure after what the query actually observes. Do not give it a more ambitious business label unless the required identity and eligibility rules have been checked.

Understand COUNT after a LEFT JOIN

Suppose you need the completed-order count for every known customer, including those with none:

sql
SELECT
    c.customer_id,
    COUNT(o.order_id) AS completed_orders
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
   AND o.status = 'completed'
GROUP BY c.customer_id
ORDER BY c.customer_id;

Expected counts are C001: 2, C002: 2, C003: 0, C004: 1, C005: 1, C006: 1 and C007: 0. We count the right-side order key because it is NULL on the placeholder row for a customer with no qualifying order.

If you replace COUNT(o.order_id) with COUNT(*), those unmatched customers receive a count of one. The LEFT JOIN preserves a row for each customer; COUNT star counts that preserved row, even when it contains no matched order.

Notice where the status condition appears. Placing it in the JOIN condition controls which orders match while retaining all customers. A right-side status condition in WHERE can remove the unmatched rows and defeat the report's purpose.

DISTINCT can count entities, but it cannot repair every join

After joining orders to item lines, COUNT(*) counts joined lines. COUNT(DISTINCT o.order_id) can recover the number of represented orders. That may be appropriate for a product report where an order with several qualifying lines should count once.

However, it does not prove that every eligible order survived the join. If some orders have no item rows and you used an INNER JOIN, their IDs have disappeared before the distinct count is calculated. You need an unmatched-record check as well.

Likewise, distinct counting does not repair an inflated monetary sum. The row-grain lesson demonstrates why summing distinct amounts can undercount separate orders with equal values.

Build a small count specification

For each count in a report, record these decisions:

QuestionExample answer
What entity is counted?Completed order
What identifies it?order_id
What makes it eligible?Status equals completed within the reporting period
Can the query contain multiple rows per entity?Yes, after joining item lines
What does missing mean?No matched order in a customer-preserving join

This specification is more durable than a rule such as “always use DISTINCT.” Sometimes repeated rows are errors; sometimes they are legitimate events. The query needs to reflect the intended entity.

Exercise: write a report with known customers, customers with at least one completed order and customers with no completed orders. The correct counts are seven, five and two. Explain why the distinct customer-ID count from the order table was six: it includes C999, which is outside the known-customer table.

NeuraPath's Data Analytics with Generative AI course develops this kind of metric reasoning alongside SQL and reporting tools. Reliable counts are especially valuable when reviewing AI-generated queries, where a fluent explanation can conceal a change in the counted entity.

Continue learning

This article is part of the SQL foundations for reliable analysis sequence. Use the neighbouring tasks when you need the prerequisite or the next application.

PK
Pankit Kumar
Lead Instructor, NeuraPath Academy

Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.

This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.

Explore Data Analytics with Generative AI
Counselling is free · no obligation

Not sure which programme fits?

Tell us your background and we will map it to the right entry point — including saying so when a cheaper programme is the better fit. A counsellor replies within one working day.