GROUP BY combines rows that have the same grouping value so aggregate functions can calculate one result for each group. For example, count the students in each class:
SELECT class,
COUNT(*) AS total_records
FROM student
GROUP BY class
ORDER BY class;
Instead of one total for the complete table, this produces one row for each class.
| class | total_records |
|---|---|
| Eight | 1 |
| Five | 3 |
| Four | 9 |
| Nine | 2 |
| Seven | 10 |
| Six | 7 |
| Three | 3 |
GROUP BY defines the groups. Aggregate functions such as COUNT(), SUM(), AVG(), MIN() and MAX() calculate values for each group.SELECT group_column,
aggregate_function(column_name)
FROM table_name
WHERE row_condition
GROUP BY group_column
HAVING group_condition
ORDER BY group_column;
WHERE, HAVING and ORDER BY are optional. Their roles are different: WHERE filters source rows, GROUP BY forms groups, HAVING filters grouped results, and ORDER BY controls result order.
Count the number of students in each class:
SELECT class,
COUNT(*) AS total_records
FROM student
GROUP BY class;
COUNT(*) counts the rows inside each class group.
Use WHERE when only selected source rows should take part in the grouping.
Count female students in each class:
SELECT class,
COUNT(*) AS total_records
FROM student
WHERE gender = 'female'
GROUP BY class
ORDER BY class;
| class | total_records |
|---|---|
| Four | 5 |
| Nine | 1 |
| Seven | 5 |
| Six | 5 |
| Three | 1 |
The filtering happens first. GROUP BY then creates groups from the remaining rows.
Several aggregate functions can be calculated for every group in one query:
SELECT class,
COUNT(*) AS total_students,
AVG(mark) AS average_mark,
MAX(mark) AS highest_mark,
MIN(mark) AS lowest_mark,
SUM(mark) AS total_mark
FROM student
GROUP BY class
ORDER BY total_students DESC,
class ASC;
| class | total_students | average_mark | highest_mark | lowest_mark | total_mark |
|---|---|---|---|---|---|
| Seven | 10 | 77.6000 | 90 | 55 | 776 |
| Four | 9 | 70.8889 | 88 | 55 | 638 |
| Six | 7 | 82.5714 | 96 | 54 | 578 |
| Three | 3 | 73.6667 | 85 | 55 | 221 |
| Five | 3 | 79.3333 | 85 | 75 | 238 |
| Nine | 2 | 41.5000 | 65 | 18 | 83 |
| Eight | 1 | 79.0000 | 79 | 79 | 79 |
Related aggregate tutorials: SUM(), AVG(), MAX() and MIN().
More than one column can define the group. This query creates a separate group for every unique class-and-gender combination:
SELECT class,
gender,
COUNT(*) AS total_students
FROM student
GROUP BY class, gender
ORDER BY class, gender;
| class | gender | total_students |
|---|---|---|
| Eight | male | 1 |
| Five | male | 3 |
| Four | female | 5 |
| Four | male | 4 |
| Nine | female | 1 |
| Nine | male | 1 |
| Seven | female | 5 |
| Seven | male | 5 |
| Six | female | 5 |
| Six | male | 2 |
| Three | female | 1 |
| Three | male | 2 |
The dedicated multiple-column GROUP BY tutorial continues this topic with more examples.
Instead of returning separate rows for male and female students, conditional aggregation can return both counts as columns in each class row:
SELECT class,
COUNT(*) AS total_students,
SUM(
CASE WHEN gender = 'male'
THEN 1
ELSE 0
END
) AS male_students,
SUM(
CASE WHEN gender = 'female'
THEN 1
ELSE 0
END
) AS female_students
FROM student
GROUP BY class
ORDER BY class;
| class | total_students | male_students | female_students |
|---|---|---|---|
| Eight | 1 | 1 | 0 |
| Five | 3 | 3 | 0 |
| Four | 9 | 4 | 5 |
| Nine | 2 | 1 | 1 |
| Seven | 10 | 5 | 5 |
| Six | 7 | 2 | 5 |
| Three | 3 | 2 | 1 |
See SQL CASE for conditional expressions.
WHERE filters rows before they are grouped. HAVING filters the grouped aggregate results.
Return only classes containing more than five students:
SELECT class,
COUNT(*) AS total_students
FROM student
GROUP BY class
HAVING COUNT(*) > 5
ORDER BY total_students DESC;
Use both when necessary:
SELECT class,
COUNT(*) AS female_students
FROM student
WHERE gender = 'female'
GROUP BY class
HAVING COUNT(*) >= 3;
A common practical use is finding values that appear more than once.
SELECT name,
COUNT(*) AS duplicate_count
FROM student2_1
GROUP BY name
HAVING COUNT(*) > 1;
| name | duplicate_count |
|---|---|
| Arnold | 2 |
| Tade Row | 3 |
GROUP BY identifies the duplicate values; removing duplicate rows is a separate operation and requires a clear rule for which record should be kept.
Delete duplicate records after identifying them
If the grouping column contains NULL values, the NULL rows form one group for that grouping expression.
SELECT class,
COUNT(*) AS total_students
FROM student3
GROUP BY class;
To display a label instead of NULL:
SELECT IFNULL(class, 'Not Known') AS class_name,
COUNT(*) AS total_students
FROM student3
GROUP BY class;
See SQL NULL values.
A grouped query should not select an arbitrary non-aggregated value that is unrelated to the grouping.
This is problematic:
-- Avoid an arbitrary non-grouped column
SELECT class,
name,
COUNT(*) AS total_students
FROM student
GROUP BY class;
There can be several different names inside one class group, so which name should SQL return?
With MySQL's ONLY_FULL_GROUP_BY SQL mode, invalid grouped selections are rejected unless the non-aggregated expression is functionally dependent on the grouping columns.
For beginner and portable queries, a safe rule is:
For example:
SELECT class,
COUNT(*) AS total_students,
AVG(mark) AS average_mark
FROM student
GROUP BY class;
ORDER BY can sort by the grouping column or an aggregate alias.
SELECT class,
COUNT(*) AS total_students
FROM student
GROUP BY class
ORDER BY total_students DESC,
class ASC;
Do not rely on GROUP BY itself to provide the desired display order. Use ORDER BY whenever order matters.
A grouped query that contains no external values can be executed directly with PDO query().
<?php
require 'config.php';
$sql="SELECT class,
COUNT(*) AS total_records
FROM student
GROUP BY class
ORDER BY class";
$stmt=$dbo->query($sql);
echo '<table class="table table-striped">';
while($row=$stmt->fetch(PDO::FETCH_ASSOC)){
$class=htmlspecialchars(
(string)$row['class'],
ENT_QUOTES,
'Windows-1252'
);
$total=(int)$row['total_records'];
echo "<tr><td>$class</td><td>$total</td></tr>";
}
echo '</table>';
If the grouped query includes values from a form or URL, use a prepared statement for those values. See fetching MySQL records with PDO.
Date and datetime columns can be grouped by derived periods such as year or month.
SELECT YEAR(sale_date) AS sale_year,
MONTH(sale_date) AS sale_month,
COUNT(*) AS total_sales
FROM sales
GROUP BY YEAR(sale_date),
MONTH(sale_date)
ORDER BY sale_year, sale_month;
Group date records by calendar year, financial year and month
EXPLAIN when a production GROUP BY query is unexpectedly slow.GROUP BY defines groups. COUNT() is only one aggregate that can be calculated for those groups.
Use WHERE to filter source rows and HAVING to filter aggregate results such as COUNT(*) > 5.
A group can contain several different values for an ungrouped column. Select grouping columns and aggregates unless a valid functional dependency applies.
Use ORDER BY when result order matters.
Rows with NULL in the grouping expression are grouped together for that expression.
GROUP BY can identify duplicate values, but deleting duplicates requires an explicit rule for which physical row to retain.
GROUP BY combines rows that share the same grouping value or combination of values so aggregate functions can calculate one result for each group.
No. GROUP BY can be used with COUNT(), SUM(), AVG(), MIN(), MAX() and other aggregate expressions.
WHERE filters source rows before grouping. HAVING filters grouped aggregate results after GROUP BY.
Yes. Each distinct combination of the listed grouping columns forms a separate group.
Group by the candidate duplicate column and use HAVING COUNT(*) greater than 1 to return values that appear more than once.
Rows containing NULL in the grouping expression are placed into one NULL group for that expression.
No. Use ORDER BY whenever you need a defined display order for grouped results.
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.
| sampat | 26-02-2009 |
| i want to display the all record from table, in which if column contain duplicate value then it will show duplicate value at once | |
| smo | 27-02-2009 |
| You have to use distinct SQL command. Read here on how to use Distinct to get unique records. | |
| alicia | 22-08-2009 |
| very nice tutorial it really helps me a lot :) | |
| karthik | 23-10-2010 |
| i want to display a column into row. | |