00:00

MySQL count() Function

Last updated: 4 min read

COUNT() answers "how many?": how many tickets are open, how many customers we have, how many different cities they come from. It has three forms that behave differently with NULLs and duplicates, and choosing the wrong one is a common source of wrong numbers.

1. The Three Forms of COUNT

COUNT(*) vs COUNT(column) vs COUNT(DISTINCT column)A city column with values Pune, Delhi, NULL, Pune, Goa. COUNT(*) counts all 5 rows, COUNT(city) counts the 4 non-NULL values, and COUNT(DISTINCT city) counts the 3 different values Pune, Delhi and Goa.citycityPuneDelhiNULLPuneGoaCOUNT(*)5every rowCOUNT(city)4non-NULL valuesCOUNT(DISTINCT city)3different non-NULL values
Figure 1: the three forms of COUNT answer three different questions.

Table: tickets (7 rows)

ticket_idcustomercityprioritystatusagentopened
1NilaPunehighopenRavi2026-03-01
2OmkarDelhilowclosedSara2026-03-01
3PrishaNULLmediumopenNULL2026-03-02
4NilaPunelowclosedRavi2026-03-03
5QasimGoahighopenSara2026-03-03
6OmkarDelhihighclosedRavi2026-03-04
7RheaPunemediumpendingNULL2026-03-05
query.sql
SELECT COUNT(*) AS all_rows, COUNT(city) AS with_city, COUNT(DISTINCT city) AS distinct_cities, COUNT(agent) AS assigned FROM tickets;

Result: 1 row

all_rowswith_citydistinct_citiesassigned
7635

2. Counting with WHERE

query.sql
SELECT COUNT(*) AS open_tickets FROM tickets WHERE status = 'open';

Result: 1 row

open_tickets
3
query.sql
SELECT COUNT(DISTINCT customer) AS customers_with_high_priority FROM tickets WHERE priority = 'high';

Result: 1 row

customers_with_high_priority
3

3. Counting per Group

query.sql
SELECT status, COUNT(*) AS tickets FROM tickets GROUP BY status ORDER BY tickets DESC;

Result: 3 rows

statustickets
open3
closed3
pending1
query.sql
SELECT city, COUNT(*) AS tickets FROM tickets GROUP BY city ORDER BY city;

Result: 4 rows

citytickets
NULL1
Delhi2
Goa1
Pune3

Rows with a NULL city form their own group, shown first.

4. Conditional Counts in One Query

Count several things at once with SUM(condition) (a condition is 1 or 0) or COUNT(CASE … END) (counts non-NULLs):

conditional.sql
SELECT COUNT(*)                                      AS total,
       SUM(status = 'open')                           AS open_cnt,
       SUM(priority = 'high')                         AS high_cnt,
       COUNT(CASE WHEN status = 'open' AND priority = 'high' THEN 1 END) AS urgent_open,
       SUM(agent IS NULL)                             AS unassigned
FROM tickets;

Result: 1 row

totalopen_cnthigh_cnturgent_openunassigned
73322
per-agent.sql
SELECT agent,
       COUNT(*)              AS total,
       SUM(status = 'open')  AS open_now,
       SUM(status = 'closed') AS closed
FROM tickets
WHERE agent IS NOT NULL
GROUP BY agent;

Result: 2 rows

agenttotalopen_nowclosed
Ravi312
Sara211

5. Showing Zero Counts with LEFT JOIN

Tanya has no tickets, so a plain GROUP BY on tickets never shows her. Start from agents and LEFT JOIN, counting a column from the right table:

query.sql
SELECT a.agent, COUNT(t.ticket_id) AS tickets
FROM agents a
LEFT JOIN tickets t ON t.agent = a.agent
GROUP BY a.agent
ORDER BY tickets DESC;

Result: 3 rows

agenttickets
Ravi3
Sara2
Tanya0
Use COUNT(t.ticket_id), not COUNT(*): COUNT(*) would count the NULL-filled row and give Tanya 1.

6. Filtering on a Count

query.sql
SELECT customer, COUNT(*) AS tickets FROM tickets GROUP BY customer HAVING COUNT(*) > 1;

Result: 2 rows

customertickets
Nila2
Omkar2

7. COUNT as a Window Function

window.sql
SELECT ticket_id, customer, opened,
       COUNT(*) OVER (PARTITION BY customer) AS customer_total,
       COUNT(*) OVER (ORDER BY opened)       AS running_total
FROM tickets
ORDER BY opened, ticket_id;

Result: 7 rows

ticket_idcustomeropenedcustomer_totalrunning_total
1Nila2026-03-0122
2Omkar2026-03-0122
3Prisha2026-03-0213
4Nila2026-03-0325
5Qasim2026-03-0315
6Omkar2026-03-0426
7Rhea2026-03-0517

8. Counting Big Tables Quickly

  • COUNT(*) and COUNT(1) are identical in MySQL; neither is faster.
  • InnoDB doesn't store an exact row count (because of MVCC), so COUNT(*) scans an index. MySQL picks the smallest index, and from 8.0.14 can scan in parallel.
  • For a quick estimate use information_schema.tables.table_rows or SHOW TABLE STATUS; it can be off by 40–50%.
  • For dashboards on huge tables, keep counters in a summary table.
query.sql
SELECT table_rows AS estimate FROM information_schema.tables WHERE table_schema = 'support' AND table_name = 'tickets';

Result: 1 row

estimate
7

9. Practice Exercises

Exercise 1: How many tickets were opened on each day?
solution.sql
SELECT opened, COUNT(*) AS tickets FROM tickets GROUP BY opened ORDER BY opened;

Result: 5 rows

openedtickets
2026-03-012
2026-03-021
2026-03-032
2026-03-041
2026-03-051
Exercise 2: For each city (ignoring unknown), count tickets and distinct customers.
solution.sql
SELECT city, COUNT(*) AS tickets, COUNT(DISTINCT customer) AS customers
FROM tickets WHERE city IS NOT NULL GROUP BY city;

Result: 3 rows

cityticketscustomers
Delhi21
Goa11
Pune32

10. Summary

  • COUNT(*) counts rows; COUNT(col) counts non-NULL values; COUNT(DISTINCT col) counts different values.
  • SUM(condition) counts matching rows, several conditions in one query.
  • LEFT JOIN + COUNT(right_column) shows groups with zero.
  • COUNT(*) on InnoDB scans an index; use estimates or summary tables for huge tables.