MySQL avg() Function
AVG() calculates the arithmetic mean: the sum of the values divided by how many there are. Average rating, average order value, average delivery time: it's one of the most used aggregate functions, and the one where NULLs most often surprise people.
1. Basic AVG
Table: ratings (9 rows)
| id | product | reviewer | stars | helpful_votes | rated_on |
|---|---|---|---|---|---|
| 1 | Earbuds | Asha | 4 | 12 | 2026-03-01 |
| 2 | Earbuds | Bilal | 5 | 30 | 2026-03-02 |
| 3 | Earbuds | Chen | NULL | 0 | 2026-03-03 |
| 4 | Earbuds | Dina | 3 | 2 | 2026-03-04 |
| 5 | Speaker | Eshan | 2 | 8 | 2026-03-02 |
| 6 | Speaker | Fatima | 4 | 15 | 2026-03-05 |
| 7 | Speaker | Gaurav | 5 | 40 | 2026-03-06 |
| 8 | Charger | Hema | 5 | 1 | 2026-03-03 |
| 9 | Charger | Ishan | 5 | 3 | 2026-03-07 |
SELECT AVG(stars) AS avg_stars, ROUND(AVG(stars), 2) AS rounded FROM ratings;
Result: 1 row
| avg_stars | rounded |
|---|---|
| 4.1250 | 4.13 |
ROUND(AVG(x), n) for display.2. AVG and NULL
SELECT AVG(stars) AS ignores_null,
AVG(COALESCE(stars, 0)) AS null_as_zero,
SUM(stars) / COUNT(*) AS sum_over_all_rows,
SUM(stars) / COUNT(stars) AS sum_over_rated
FROM ratings WHERE product = 'Earbuds';
Result: 1 row
| ignores_null | null_as_zero | sum_over_all_rows | sum_over_rated |
|---|---|---|---|
| 4.0000 | 3.0000 | 3.0000 | 4.0000 |
3. Average per Group
SELECT product, COUNT(stars) AS ratings, ROUND(AVG(stars), 2) AS avg_stars FROM ratings GROUP BY product ORDER BY avg_stars DESC;
Result: 3 rows
| product | ratings | avg_stars |
|---|---|---|
| Charger | 2 | 5.00 |
| Earbuds | 3 | 4.00 |
| Speaker | 3 | 3.67 |
SELECT product, ROUND(AVG(stars), 2) AS avg_stars FROM ratings GROUP BY product HAVING AVG(stars) >= 4;
Result: 2 rows
| product | avg_stars |
|---|---|
| Earbuds | 4.00 |
| Charger | 5.00 |
4. AVG(DISTINCT)
SELECT AVG(stars) AS avg_all, AVG(DISTINCT stars) AS avg_of_distinct_values FROM ratings;
Result: 1 row
| avg_all | avg_of_distinct_values |
|---|---|
| 4.1250 | 3.5000 |
AVG(DISTINCT) averages each different value once (2, 3, 4, 5 here). It answers a different question and is rarely what a report needs.
5. Weighted Averages
Give helpful reviews more weight: multiply each rating by its votes, then divide by the total votes:
SELECT product,
ROUND(AVG(stars), 2) AS simple_avg,
ROUND(SUM(stars * helpful_votes) / SUM(helpful_votes), 2) AS weighted_avg
FROM ratings
WHERE stars IS NOT NULL
GROUP BY product;
Result: 3 rows
| product | simple_avg | weighted_avg |
|---|---|---|
| Earbuds | 4.00 | 4.64 |
| Speaker | 3.67 | 4.38 |
| Charger | 5.00 | 5.00 |
6. Comparing Rows with the Average
SELECT reviewer, product, stars
FROM ratings
WHERE stars > (SELECT AVG(stars) FROM ratings)
ORDER BY stars DESC, reviewer;
Result: 4 rows
| reviewer | product | stars |
|---|---|---|
| Bilal | Earbuds | 5 |
| Gaurav | Speaker | 5 |
| Hema | Charger | 5 |
| Ishan | Charger | 5 |
Per product, using a window function so every row keeps its details:
SELECT product, reviewer, stars,
ROUND(AVG(stars) OVER (PARTITION BY product), 2) AS product_avg,
stars - ROUND(AVG(stars) OVER (PARTITION BY product), 2) AS vs_avg
FROM ratings
WHERE stars IS NOT NULL
ORDER BY product, stars DESC;
Result: 8 rows
| product | reviewer | stars | product_avg | vs_avg |
|---|---|---|---|---|
| Charger | Hema | 5 | 5.00 | 0.00 |
| Charger | Ishan | 5 | 5.00 | 0.00 |
| Earbuds | Bilal | 5 | 4.00 | 1.00 |
| Earbuds | Asha | 4 | 4.00 | 0.00 |
| Earbuds | Dina | 3 | 4.00 | -1.00 |
| Speaker | Gaurav | 5 | 3.67 | 1.33 |
| Speaker | Fatima | 4 | 3.67 | 0.33 |
| Speaker | Eshan | 2 | 3.67 | -1.67 |
7. Moving Averages
A moving average smooths out daily ups and downs. Average of the current and two previous ratings, by date:
SELECT rated_on, product, stars,
ROUND(AVG(stars) OVER (ORDER BY rated_on, id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS moving_avg_3
FROM ratings
WHERE stars IS NOT NULL
ORDER BY rated_on, id;
Result: 8 rows
| rated_on | product | stars | moving_avg_3 |
|---|---|---|---|
| 2026-03-01 | Earbuds | 4 | 4.00 |
| 2026-03-02 | Earbuds | 5 | 4.50 |
| 2026-03-02 | Speaker | 2 | 3.67 |
| 2026-03-03 | Charger | 5 | 4.00 |
| 2026-03-04 | Earbuds | 3 | 3.33 |
| 2026-03-05 | Speaker | 4 | 4.00 |
| 2026-03-06 | Speaker | 5 | 4.00 |
| 2026-03-07 | Charger | 5 | 4.67 |
8. Pitfalls
- An average of averages isn't the overall average when groups differ in size; average the raw rows or use weights.
- Averages hide outliers; check MIN, MAX or a median as well.
- AVG of an empty set is NULL; use COALESCE if you need 0.
SELECT ROUND(AVG(avg_stars), 4) AS average_of_averages,
(SELECT ROUND(AVG(stars), 4) FROM ratings) AS true_average
FROM (SELECT product, AVG(stars) AS avg_stars FROM ratings GROUP BY product) t;
Result: 1 row
| average_of_averages | true_average |
|---|---|
| 4.2222 | 4.1250 |
9. Summary
AVG(col)= sum of non-NULL values ÷ number of non-NULL values.- Use COALESCE if missing values should count as 0; ROUND for display.
- Group with GROUP BY, filter averages with HAVING.
- Weighted average = SUM(value × weight) ÷ SUM(weight); window AVG gives per-row comparisons and moving averages.