MySQL AVG() returns the arithmetic mean of the non-NULL numeric values in an expression. It is commonly used for averages such as marks, prices, ratings, quantities, and grouped report values.
SELECT AVG(mark) AS avg_mark
FROM student;
For the six sample rows used below, the result is:
| avg_mark |
|---|
| 65.0000 |
(social + math + science) / 3.0.SELECT AVG(numeric_expression)
FROM table_name;
AVG() ignores NULL values and divides the sum of the remaining numeric values by the number of non-NULL values.
The first examples use these rows from the student table:
| id | name | class | mark |
|---|---|---|---|
| 1 | John Deo | Four | 75 |
| 2 | Max Ruin | Three | 85 |
| 3 | Arnold | Three | 55 |
| 4 | Krish Star | Four | 60 |
| 5 | John Mike | Four | 60 |
| 6 | Alex John | Four | 55 |
SELECT AVG(mark) AS avg_mark
FROM student;
| avg_mark |
|---|
| 65.0000 |
A WHERE clause filters rows before AVG() is calculated.
SELECT AVG(mark) AS avg_mark
FROM student
WHERE mark > 55;
Only marks greater than 55 contribute to the result.
Use GROUP BY when you need one average for each class rather than one average for the complete table.
SELECT class,
AVG(mark) AS avg_mark
FROM student
GROUP BY class
ORDER BY class;
| class | avg_mark |
|---|---|
| Four | 62.5000 |
| Three | 70.0000 |
You can first remove unwanted rows and then calculate an average for each remaining group:
SELECT class,
AVG(mark) AS avg_mark
FROM student
WHERE mark > 55
GROUP BY class
ORDER BY class;
| class | avg_mark |
|---|---|
| Four | 65.0000 |
| Three | 85.0000 |
WHERE filters individual rows before grouping. HAVING filters the grouped result after AVG() has been calculated.
SELECT class,
AVG(mark) AS avg_mark
FROM student
GROUP BY class
HAVING AVG(mark) > 75
ORDER BY avg_mark DESC;
This returns only classes whose calculated average mark is greater than 75.
To calculate the average for selected rows, combine IN with AVG():
SELECT AVG(mark) AS avg_mark
FROM student
WHERE id IN (1, 2, 3, 4);
For IDs 1 through 4 in the sample table, the average is 68.7500.
A subquery can calculate an average that is then used by the outer query. For example, list students in class Six whose mark is below the class average:
SELECT id,
name,
class,
mark
FROM student
WHERE class = 'Six'
AND mark < (
SELECT AVG(mark)
FROM student
WHERE class = 'Six'
)
ORDER BY mark;
The inner query first calculates the class average. The outer query then returns only students below that value.
Read more about SQL subqueries
AVG() ignores NULL values. NULL is not treated as zero.
| Student | Mark |
|---|---|
| Max Ruin | 50 |
| Arnold | NULL |
| Krish Star | NULL |
| John Mike | 50 |
The average is:
(50 + 50) / 2 = 50
It is not 100 / 4 = 25, because the two NULL values are not part of the denominator.
See SQL NULL values for the difference between NULL, zero, and an empty value.
AVG(DISTINCT expression) averages each distinct non-NULL value only once.
CREATE TABLE plus2_price (
product VARCHAR(20) NOT NULL,
price_date DATE NOT NULL,
price DECIMAL(10,2) NOT NULL
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4;
INSERT INTO plus2_price
(product, price_date, price)
VALUES
('Product1', '2017-02-24', 10),
('Product1', '2017-02-24', 10),
('Product1', '2017-02-24', 10),
('Product1', '2017-02-25', 20);
The average of all four rows is:
SELECT AVG(price) AS avg_price
FROM plus2_price;
Result: 12.50.
If the requirement is specifically to average the unique price values 10 and 20:
SELECT AVG(DISTINCT price) AS avg_unique_price
FROM plus2_price;
Result: 15.00.
If the requirement is to give each day equal weight, first calculate one average per day and then average those daily values:
SELECT AVG(daily_avg) AS avg_of_daily_prices
FROM (
SELECT price_date,
AVG(price) AS daily_avg
FROM plus2_price
GROUP BY price_date
) AS daily_prices;
This distinction is important whenever repeated measurements exist within each day or category.
Suppose student3 stores marks for three subjects in separate columns. To calculate each student's total and average:
SELECT id,
name,
social,
math,
science,
social + math + science AS total,
(social + math + science) / 3.0 AS avg_mark
FROM student3
ORDER BY id;
| id | name | social | math | science | total | avg_mark |
|---|---|---|---|---|---|---|
| 2 | Max Ruin | 85 | 85 | 56 | 226 | 75.3333 |
| 3 | Arnold | 55 | 75 | 40 | 170 | 56.6667 |
| 4 | Krish Star | 60 | 70 | 50 | 180 | 60.0000 |
| 5 | John Mike | 60 | 90 | 80 | 230 | 76.6667 |
| 6 | Alex John | 55 | 80 | 90 | 225 | 75.0000 |
| 7 | My John Rob | 78 | 70 | 60 | 208 | 69.3333 |
| 8 | Asruid | 85 | 90 | 80 | 255 | 85.0000 |
| 9 | Tes Qry | 78 | 70 | 60 | 208 | 69.3333 |
| 10 | Big John | 55 | 55 | 40 | 150 | 50.0000 |
Calculated values can usually be derived when needed. Storing the same average in another column creates duplicated data that can become stale when a mark changes.
If a reporting table intentionally stores the student's three-subject average, the direct calculation is simpler than joining the table to itself:
UPDATE student3_avg
SET average = (social + math + science) / 3.0;
For class averages, calculate the aggregate from the source rows with AVG() and GROUP BY. Store it only when there is a clear reporting, caching, or historical requirement.
Download the existing sample table dump used for the average examples.
The existing calculated-value insertion tutorial shows another reporting-table approach.
Use ORDER BY on the calculated alias to display the highest average first:
SELECT id,
name,
social,
math,
science,
social + math + science AS total,
(social + math + science) / 3.0 AS avg_mark
FROM student3
ORDER BY avg_mark DESC, id;
No GROUP BY id is needed here because each source row already represents one student.
After connecting to MySQL with PDO, a static SELECT query can be executed with query():
<?php
$sql="SELECT id,
name,
social,
math,
science,
social + math + science AS total,
(social + math + science) / 3.0 AS avg_mark
FROM student3
ORDER BY avg_mark DESC, id";
$stmt=$dbo->query($sql);
foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row){
echo '<p>'
.htmlspecialchars(
$row['name'],
ENT_QUOTES,
'Windows-1252'
)
.' : '
.htmlspecialchars(
(string)$row['avg_mark'],
ENT_QUOTES,
'Windows-1252'
)
.'</p>';
}
The SQL is static, so no prepared parameter is required in this example. When user input is added to the query, use a prepared statement and bind the values.
MySQL FORMAT() can format a number for display:
SELECT id,
name,
FORMAT(
(social + math + science) / 3.0,
2
) AS avg_display
FROM student3;
FORMAT() returns formatted text. Keep the unformatted numeric value when the result will be sorted, compared, or used in more arithmetic, and format it only for final display.See SQL FORMAT() for more examples.
AVG() ignores NULL values. Replacing NULL with zero changes both the sum and the denominator, so only do that when zero is the intended business meaning.
If one row already contains one student's three subject marks, calculate the row expression directly. GROUP BY id is unnecessary unless multiple source rows must actually be aggregated.
DISTINCT removes duplicate numeric values, not duplicate days. Group by date when the requirement is date-based.
Use WHERE before aggregation and HAVING after GROUP BY when filtering by the calculated average.
Keep averages numeric while sorting, comparing, or calculating. Format them for display at the end.
Stored averages can become inconsistent with the source marks. Calculate them on demand unless a reporting or historical requirement justifies storage.
Download the sample average-table SQL dump
Download the student3 SQL dump
Other filters such as BETWEEN can also be combined with AVG() when the average must be calculated for a selected range.
AVG() returns the arithmetic mean of the non-NULL numeric values produced by an expression.
No. NULL values are ignored when MySQL calculates both the sum and the number of values used for the average.
Use AVG(mark) together with GROUP BY class.
WHERE filters source rows before AVG() is calculated. HAVING filters grouped results after the aggregate has been calculated.
It calculates the average after duplicate non-NULL values in that expression have been removed.
Use an arithmetic expression such as (social + math + science) / 3.0 when all three columns are part of the same student's row.
Usually calculate them when needed. Store a derived average only when a reporting, caching, or historical requirement justifies keeping duplicated data synchronized.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.
| lotr | 04-04-2009 |
| Note that on some mysql servers the autorounding is bad handled ... | |
| chandradevi | 23-01-2013 |
| it is good | |
| Karthikeyan.M | 14-02-2013 |
| order a class students by mark and list out by max,aveg | |
| chika Augustine | 07-06-2016 |
| Pleas I want to check average for the three terms for promotion of class | |
| Yahaya Abdulkadir | 26-08-2017 |
| please I would like to know how to sort a database student average score in result processing? | |
| smo1234 | 31-10-2017 |
| Use order by query | |