Skip to main content
Database Administration & Troubleshooting

SQL COUNT() Reference: Syntax, Performance & Troubleshooting

sql query count rows is the SQL Server T-SQL operation to return the number of rows in a table or result set, using COUNT(*), COUNT(column), or COUNT(DISTINCT expression).

SELECT COUNT(*) FROM table_name;

What is COUNT and when to use it?

COUNT is an aggregate function that counts rows satisfying the query predicate. COUNT(*) returns the total number of rows, including duplicates and NULLs. COUNT(column) counts non-NULL values in that column. COUNT(DISTINCT column) counts unique non-NULL values.

Tested on SQL Server 2022 (16.0) with AdventureWorks2022 sample database.

Syntax

-- Full syntax diagram for COUNT
COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )
       [ OVER ( [ partition_by_clause ] [ order_by_clause ] [ ROW_or_RANGE_clause ] ) ]

-- Common forms:
SELECT COUNT(*) FROM table_name;                     -- all rows
SELECT COUNT(ALL expression) FROM table_name;        -- non-NULL values (ALL is default)
SELECT COUNT(DISTINCT expression) FROM table_name;   -- unique non-NULL values
SELECT COUNT(*) OVER (PARTITION BY column) ...       -- running count within partitions

Usage Examples

1. Basic row count with WHERE filter

-- Count orders with status 'Shipped'
SELECT COUNT(*) AS ShippedCount
FROM Sales.SalesOrderHeader
WHERE Status = 5;

Scans the clustered index of the table, filtering on the Status column. For large tables, ensure an index on Status INCLUDE (primary key) to avoid full scan.

See also  NTILE in SQL: Syntax, Examples, and Troubleshooting

2. Count distinct product IDs per territory

-- How many different products were ordered in each territory?
SELECT TerritoryID, COUNT(DISTINCT ProductID) AS UniqueProducts
FROM Sales.SalesOrderDetail
GROUP BY TerritoryID;

This eliminates duplicate ProductID values per group. Performance depends on the cardinality of ProductID; consider a filtered index if this query runs frequently.

3. Running row count with OVER

-- Cumulative count of orders per customer, ordered by order date
SELECT CustomerID, OrderDate,
       COUNT(*) OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS OrderSeq
FROM Sales.SalesOrderHeader
ORDER BY CustomerID, OrderDate;

Each row shows the number of orders up to that date for that customer. The OVER clause uses RANGE UNBOUNDED PRECEDING by default when ORDER BY is specified.

Error Codes and Troubleshooting

Error / Condition Root Cause Resolution
Msg 8115, Arithmetic overflow COUNT_BIG exceeds bigint limit (unlikely) or implicit conversion fails Use COUNT_BIG only when necessary. Check input data types with SELECT SQL_VARIANT_PROPERTY(value, 'BaseType').
ANSI_WARNINGS severity 16 COUNT on expression with NULL in a constraint violation Set SET ANSI_WARNINGS OFF (not recommended) or filter out NULLs explicitly.
Slow COUNT(*) on large table Full table scan required, no index covering all rows Use sys.dm_db_partition_stats for near-real-time row count:
SELECT SUM(row_count) FROM sys.dm_db_partition_stats WHERE object_id = OBJECT_ID('table_name') AND index_id IN (0,1);
COUNT(DISTINCT) high memory grant Large distinct count on unsorted column, spills to tempdb Create a non-clustered index on the distinct column; consider using approximate count with APPROX_COUNT_DISTINCT (SQL Server 2019+).

Arguments

Argument Type Default Description
ALL Keyword Applied Applies the aggregate to all values. Default behavior.
DISTINCT Keyword N/A Counts only distinct non-NULL values of the expression.
expression Any column or expression N/A A column or computed value. NULLs are ignored unless using COUNT(*).
* Asterisk N/A Counts all rows regardless of NULLs or duplicates. Takes no parameters.
OVER Clause None Defines a window for the COUNT. Requires PARTITION BY and optional ordering.
See also  Union In SQL: Syntax, Examples, Flags & Production Guide

Return types

COUNT returns int. COUNT_BIG returns bigint (use when row count may exceed 2,147,483,647).

Performance Considerations

For production row counts on large tables, prefer sys.dm_db_partition_stats over a full COUNT(*) scan. Validate accuracy by comparing against a full count during off-peak hours once per maintenance window. Use COUNT_BIG if row count exceeds int range (Msg 8115). Filter NULLs explicitly to avoid ANSI_WARNINGS errors. For COUNT(DISTINCT), create a non-clustered index on the column to reduce memory grants.

Frequently Asked Questions

What is the difference between COUNT(*) and COUNT(column_name)?

Answer: COUNT(*) counts all rows including NULLs; COUNT(col) counts non-NULL values in that column.

Use COUNT(*) for total rows regardless of NULLs. Use COUNT(column) to count non-missing data. COUNT(DISTINCT column) is for unique non-NULL values. Example:

SELECT COUNT(*) AS total_rows,
       COUNT(email) AS emails_provided,
       COUNT(DISTINCT department) AS distinct_depts
FROM employees;

When should I use the GROUP BY clause with COUNT?

Answer: Use GROUP BY with COUNT to obtain row counts per group.

Whenever you need aggregated counts by a categorical column, add GROUP BY column. Example:

SELECT department, COUNT(*) AS employee_count
FROM employees
GROUP BY department;

This produces one row per department with the number of employees in each.

How do I fix “ERROR: column ‘x’ must appear in the GROUP BY clause or be used in an aggregate function” in PostgreSQL?

Answer: Add column ‘x’ to the GROUP BY clause or wrap it in an aggregate function.

This error occurs when mixing grouped and non-grouped columns. Fix by including the column in GROUP BY or using an aggregate:

-- Wrong:
SELECT department, name, COUNT(*) FROM employees GROUP BY department;
-- Fix (if you want name per group, use MIN/MAX):
SELECT department, MIN(name) AS sample_name, COUNT(*) FROM employees GROUP BY department;

Does SELECT COUNT(*) work the same on Amazon Redshift, Google BigQuery, and Azure Synapse?

Answer: Yes, COUNT(*) is standard SQL and works identically across all major cloud data warehouses.

See also  SQL Server DATEADD Function: Syntax, Parameters, and Examples

Each platform supports COUNT(*) with identical semantics. For approximate distinct counts, use (Redshift) APPROXIMATE COUNT(DISTINCT), (BigQuery) APPROX_COUNT_DISTINCT, (Azure Synapse) APPROX_COUNT_DISTINCT. Example identical usage:

SELECT COUNT(*) FROM orders WHERE status = 'shipped';

What is the fastest way to count rows in a large MySQL table?

Answer: Use SELECT COUNT(*) with a primary key or smallest secondary index.

InnoDB cannot cache row counts; force index usage for speed:

SELECT COUNT(*) FROM large_table FORCE INDEX (PRIMARY);

For approximate counts, query SHOW TABLE STATUS LIKE 'large_table' and check Rows column. Avoid COUNT(1); it is equivalent but not faster.