What is the difference between COUNT(*) and COUNT(column), and when does it actually matter?
COUNT(*) counts the rows in the group. COUNT(column) counts the rows where that column is not NULL. When the column is declared NOT NULL the two are always identical, and when the column is nullable they can differ.
CREATE TABLE tickets (
id INTEGER PRIMARY KEY,
title VARCHAR(160) NOT NULL,
assigned_to INTEGER
);
INSERT INTO tickets (id, title, assigned_to) VALUES
(1, 'Login page times out', 11),
(2, 'Invoice PDF is blank', 12),
(3, 'Export times out', NULL),
(4, 'Dark mode request', NULL),
(5, 'Slow search results', 11);
SELECT COUNT(*) AS all_rows,
COUNT(assigned_to) AS assigned_rows,
COUNT(*) - COUNT(assigned_to) AS unassigned_rows
FROM tickets;Three rows come back in one line: 5, 3 and 2. There are five tickets, three of them have an assignee, and two are unassigned. COUNT(assigned_to) is 3 rather than 5 because NULL values are not counted.
The difference matters most in three situations. When you join with an outer join, the outer-joined rows arrive with NULLs in the right-hand columns, so COUNT(*) counts them and COUNT(right.column) does not. When you are checking data completeness, COUNT(column) is the tool that tells you how many values are actually present. And when you compute an average, the denominator is the count of non-NULL values, not the row count.
Two more details. COUNT(DISTINCT column) is a third thing entirely: it counts distinct non-NULL values, which is how you find out how many different assignees there are. And the performance difference is real but usually small — COUNT(*) can be answered by an index-only scan, while COUNT(column) has to read the column itself unless the index covers it. On a very wide table that can matter.
The rule to remember: COUNT(*) asks how many rows, COUNT(column) asks how many values. Choose deliberately, because the gap between them is exactly the number of missing values, which is often the number the report is trying to surface.