MySQL count() Function
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
Table: tickets (7 rows)
| ticket_id | customer | city | priority | status | agent | opened |
|---|---|---|---|---|---|---|
| 1 | Nila | Pune | high | open | Ravi | 2026-03-01 |
| 2 | Omkar | Delhi | low | closed | Sara | 2026-03-01 |
| 3 | Prisha | NULL | medium | open | NULL | 2026-03-02 |
| 4 | Nila | Pune | low | closed | Ravi | 2026-03-03 |
| 5 | Qasim | Goa | high | open | Sara | 2026-03-03 |
| 6 | Omkar | Delhi | high | closed | Ravi | 2026-03-04 |
| 7 | Rhea | Pune | medium | pending | NULL | 2026-03-05 |
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_rows | with_city | distinct_cities | assigned |
|---|---|---|---|
| 7 | 6 | 3 | 5 |
2. Counting with WHERE
SELECT COUNT(*) AS open_tickets FROM tickets WHERE status = 'open';
Result: 1 row
| open_tickets |
|---|
| 3 |
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
SELECT status, COUNT(*) AS tickets FROM tickets GROUP BY status ORDER BY tickets DESC;
Result: 3 rows
| status | tickets |
|---|---|
| open | 3 |
| closed | 3 |
| pending | 1 |
SELECT city, COUNT(*) AS tickets FROM tickets GROUP BY city ORDER BY city;
Result: 4 rows
| city | tickets |
|---|---|
| NULL | 1 |
| Delhi | 2 |
| Goa | 1 |
| Pune | 3 |
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):
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
| total | open_cnt | high_cnt | urgent_open | unassigned |
|---|---|---|---|---|
| 7 | 3 | 3 | 2 | 2 |
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
| agent | total | open_now | closed |
|---|---|---|---|
| Ravi | 3 | 1 | 2 |
| Sara | 2 | 1 | 1 |
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:
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
| agent | tickets |
|---|---|
| Ravi | 3 |
| Sara | 2 |
| Tanya | 0 |
COUNT(t.ticket_id), not COUNT(*): COUNT(*) would count the NULL-filled row and give Tanya 1.6. Filtering on a Count
SELECT customer, COUNT(*) AS tickets FROM tickets GROUP BY customer HAVING COUNT(*) > 1;
Result: 2 rows
| customer | tickets |
|---|---|
| Nila | 2 |
| Omkar | 2 |
7. COUNT as a Window Function
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_id | customer | opened | customer_total | running_total |
|---|---|---|---|---|
| 1 | Nila | 2026-03-01 | 2 | 2 |
| 2 | Omkar | 2026-03-01 | 2 | 2 |
| 3 | Prisha | 2026-03-02 | 1 | 3 |
| 4 | Nila | 2026-03-03 | 2 | 5 |
| 5 | Qasim | 2026-03-03 | 1 | 5 |
| 6 | Omkar | 2026-03-04 | 2 | 6 |
| 7 | Rhea | 2026-03-05 | 1 | 7 |
8. Counting Big Tables Quickly
COUNT(*)andCOUNT(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_rowsorSHOW TABLE STATUS; it can be off by 40–50%. - For dashboards on huge tables, keep counters in a summary table.
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?
SELECT opened, COUNT(*) AS tickets FROM tickets GROUP BY opened ORDER BY opened;
Result: 5 rows
| opened | tickets |
|---|---|
| 2026-03-01 | 2 |
| 2026-03-02 | 1 |
| 2026-03-03 | 2 |
| 2026-03-04 | 1 |
| 2026-03-05 | 1 |
Exercise 2: For each city (ignoring unknown), count tickets and distinct customers.
SELECT city, COUNT(*) AS tickets, COUNT(DISTINCT customer) AS customers
FROM tickets WHERE city IS NOT NULL GROUP BY city;
Result: 3 rows
| city | tickets | customers |
|---|---|---|
| Delhi | 2 | 1 |
| Goa | 1 | 1 |
| Pune | 3 | 2 |
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.