MySQL Aggregate Function
An aggregate function takes many rows and returns one value: a count, a total, an average, the smallest or largest value. They power every summary report, from "how many orders today?" to "average score per class". This page introduces all of them; the following tutorials go deeper into each.
1. What Aggregates Do
Table: results (9 rows)
| id | student | class | subject | score |
|---|---|---|---|---|
| 1 | Aditi | 10A | Maths | 72 |
| 2 | Aditi | 10A | Science | 85 |
| 3 | Bhanu | 10A | Maths | NULL |
| 4 | Bhanu | 10A | Science | 91 |
| 5 | Charu | 10B | Maths | 64 |
| 6 | Charu | 10B | Science | 58 |
| 7 | Dhruv | 10B | Maths | 97 |
| 8 | Dhruv | 10B | Science | 88 |
| 9 | Elina | 10B | Maths | 81 |
2. The Aggregate Functions
| Function | Returns | Tutorial |
|---|---|---|
COUNT(*) / COUNT(col) | Number of rows / non-NULL values | count() |
SUM(col) | Total of the values | sum() |
AVG(col) | Average (mean) | avg() |
MIN(col) / MAX(col) | Smallest / largest value | min(), max() |
GROUP_CONCAT(col) | Values joined into one string | |
JSON_ARRAYAGG(col), JSON_OBJECTAGG(k, v) | Values collected into JSON | |
STDDEV_POP, STDDEV_SAMP, VAR_POP, VAR_SAMP | Spread of the values | |
BIT_AND, BIT_OR, BIT_XOR | Bitwise combination |
3. Aggregating a Whole Table
SELECT COUNT(*) AS rows_total,
COUNT(score) AS scores_recorded,
SUM(score) AS total,
ROUND(AVG(score), 2) AS average,
MIN(score) AS lowest,
MAX(score) AS highest
FROM results;
Result: 1 row
| rows_total | scores_recorded | total | average | lowest | highest |
|---|---|---|---|---|---|
| 9 | 8 | 636 | 79.50 | 58 | 97 |
4. How Aggregates Treat NULL
COUNT(*)counts rows, including rows with NULLs.- All other aggregates ignore NULL values (Bhanu's missing Maths score above).
- On an empty set,
COUNTreturns 0 and the others return NULL.
SELECT COUNT(*) AS n, SUM(score) AS total, AVG(score) AS avg_score, MAX(score) AS top
FROM results WHERE subject = 'History';
Result: 1 row
| n | total | avg_score | top |
|---|---|---|---|
| 0 | NULL | NULL | NULL |
SELECT COALESCE(SUM(score), 0) AS total_or_zero FROM results WHERE subject = 'History';
Result: 1 row
| total_or_zero |
|---|
| 0 |
5. Aggregates per Group
SELECT class, subject, COUNT(score) AS students, ROUND(AVG(score), 1) AS avg_score, MAX(score) AS best
FROM results
GROUP BY class, subject
ORDER BY class, subject;
Result: 4 rows
| class | subject | students | avg_score | best |
|---|---|---|---|---|
| 10A | Maths | 1 | 72.0 | 72 |
| 10A | Science | 2 | 88.0 | 91 |
| 10B | Maths | 3 | 80.7 | 97 |
| 10B | Science | 2 | 73.0 | 88 |
6. DISTINCT Inside an Aggregate
SELECT COUNT(DISTINCT student) AS students, COUNT(DISTINCT class) AS classes, SUM(DISTINCT score) AS sum_of_distinct_scores FROM results;
Result: 1 row
| students | classes | sum_of_distinct_scores |
|---|---|---|
| 5 | 2 | 636 |
7. GROUP_CONCAT and JSON Aggregates
SELECT class,
GROUP_CONCAT(DISTINCT student ORDER BY student SEPARATOR ', ') AS students,
JSON_ARRAYAGG(score) AS all_scores
FROM results
GROUP BY class;
Result: 2 rows
| class | students | all_scores |
|---|---|---|
| 10A | Aditi, Bhanu | [72, 85, null, 91] |
| 10B | Charu, Dhruv, Elina | [64, 58, 97, 88, 81] |
GROUP_CONCAT output is cut at
group_concat_max_len (1024 bytes by default). Raise it with SET SESSION group_concat_max_len = 100000; for long lists.8. Statistical Aggregates
SELECT subject,
ROUND(AVG(score), 2) AS mean,
ROUND(STDDEV_POP(score), 2) AS std_dev,
ROUND(VAR_POP(score), 2) AS variance
FROM results
GROUP BY subject;
Result: 2 rows
| subject | mean | std_dev | variance |
|---|---|---|---|
| Maths | 78.50 | 12.26 | 150.25 |
| Science | 80.50 | 13.16 | 173.25 |
9. Aggregates as Window Functions
Add OVER (…) and an aggregate no longer collapses rows: each row keeps its details plus the group's value. Compare each score with its class average:
SELECT student, class, subject, score,
ROUND(AVG(score) OVER (PARTITION BY class, subject), 1) AS class_avg,
score - ROUND(AVG(score) OVER (PARTITION BY class, subject), 1) AS diff
FROM results
WHERE subject = 'Maths'
ORDER BY class, score DESC;
Result: 5 rows
| student | class | subject | score | class_avg | diff |
|---|---|---|---|---|---|
| Aditi | 10A | Maths | 72 | 72.0 | 0.0 |
| Bhanu | 10A | Maths | NULL | 72.0 | NULL |
| Dhruv | 10B | Maths | 97 | 80.7 | 16.3 |
| Elina | 10B | Maths | 81 | 80.7 | 0.3 |
| Charu | 10B | Maths | 64 | 80.7 | -16.7 |
10. Rules to Remember
- You can't use an aggregate in WHERE; use HAVING for conditions on aggregates.
- Every non-aggregated column in SELECT must be in GROUP BY.
- Aggregates can't be nested directly (
MAX(AVG(x))); use a sub-query or CTE. - Integer SUM returns DECIMAL; AVG of integers returns DECIMAL with 4 extra decimal places.
SELECT MAX(avg_score) AS best_class_average
FROM (SELECT class, AVG(score) AS avg_score FROM results GROUP BY class) t;
Result: 1 row
| best_class_average |
|---|
| 82.6667 |
11. Summary
- Aggregates (COUNT, SUM, AVG, MIN, MAX, GROUP_CONCAT, JSON_ARRAYAGG, STDDEV…) turn many rows into one value.
- They ignore NULLs, except COUNT(*).
- Combine with GROUP BY for per-group results and HAVING to filter groups.
- Use DISTINCT inside to aggregate unique values; add OVER() to keep row details.
Was this tutorial helpful?