SQL GROUP BY Explained with Examples
GROUP BY collapses rows that share a value into one row per group, so you can count, sum or average each group. It is behind most analytics questions: revenue per month, users per country, orders per customer.
Examples run in MySQL 8 and PostgreSQL · Updated
In short
- Every selected column must be grouped or wrapped in an aggregate.
COUNT(*)counts rows;COUNT(col)skips NULLs.- NULL values form their own group.
- Filter rows with
WHEREbefore grouping, groups withHAVINGafter.
What happens to the rows
Picture the table sorted into piles, one pile per dept_id. Each pile becomes a single output row, and aggregate functions (COUNT, SUM, AVG, MIN, MAX) summarise the pile.
Because a pile becomes one row, you cannot select a plain column like name unless it is in GROUP BY: the database would not know which name from the pile to show. PostgreSQL rejects it; MySQL rejects it too under its default ONLY_FULL_GROUP_BY mode.
NULL is a group too
Rows where the grouping column is NULL are not dropped; they form one NULL group. In the example below, Zoya (no department) appears as the NULL group. Where that group sorts differs between databases, so sort by something explicit.
The example tables
Every example on this page uses these tables. They are small on purpose, so you can check each result by eye.
employees
| emp_id | name | dept_id | salary | manager_id | hire_date |
|---|---|---|---|---|---|
| 1 | Aarav | 10 | 95000 | NULL | 2021-03-01 |
| 2 | Diya | 10 | 88000 | 1 | 2022-06-15 |
| 3 | Kabir | 10 | 72000 | 1 | 2023-01-10 |
| 4 | Meera | 20 | 64000 | NULL | 2020-11-20 |
| 5 | Rohan | 20 | 58000 | 4 | 2024-02-05 |
| 6 | Isha | 30 | 61000 | NULL | 2022-09-12 |
| 7 | Arjun | 20 | 64000 | 4 | 2023-07-30 |
| 8 | Zoya | NULL | 52000 | NULL | 2024-05-01 |
Examples
Count and average per department
One row per dept_id, including the NULL group for Zoya. ROUND keeps the average readable.
SELECT dept_id, COUNT(*) AS employees, ROUND(AVG(salary)) AS avg_salary
FROM employees
GROUP BY dept_id
ORDER BY employees DESC, avg_salary DESC;| dept_id | employees | avg_salary |
|---|---|---|
| 10 | 3 | 85000 |
| 20 | 3 | 62000 |
| 30 | 1 | 61000 |
| NULL | 1 | 52000 |
Several aggregates at once
WHERE removes Zoya before grouping; each department shows its top salary and earliest hire date.
SELECT dept_id, MAX(salary) AS top_salary, MIN(hire_date) AS first_hire
FROM employees
WHERE dept_id IS NOT NULL
GROUP BY dept_id
ORDER BY dept_id;| dept_id | top_salary | first_hire |
|---|---|---|
| 10 | 95000 | 2021-03-01 |
| 20 | 64000 | 2020-11-20 |
| 30 | 61000 | 2022-09-12 |
Common mistakes
- Selecting an ungrouped column. Add the column to
GROUP BY, wrap it in an aggregate, or use a window function if you need row-level detail next to group totals. - COUNT(col) vs COUNT(*).
COUNT(manager_id)skips employees without a manager. UseCOUNT(*)to count rows,COUNT(DISTINCT col)to count unique values. - Integer averages. Averages and ratios of integers can lose decimals in some databases. Multiply by
1.0or useROUND(…, 2)when the question asks for decimals.
Practise GROUP BY
Interview questions that use this concept, easiest first. Each one has the step-by-step visualizer and answer checking.
Multilingual Language Speakers
Identify users who speak at least two distinct target languages ('English', 'Spanish', 'German') from skill profile records.
Daily Active Users (DAU)
Compute the daily count of unique active users from raw product activity event timestamps.
Ad Click-Through Rate (CTR)
Calculate the percentage CTR per ad campaign rounded to 2 decimals, safely handling campaigns with zero impressions.
Play Store: Top Rated App Categories
Find categories with at least 3 apps rated 4.0 or higher, returning the category, app count, and average rating rounded to 2 decimals.
Google Maps: High-Impact Local Guides
Find verified reviewers who submitted at least 2 photo reviews in San Francisco or Mountain View.
Google Drive: External Sensitive File Shares
Audit sensitive files shared with non-Google email addresses to identify potential data exposure risks.
Want a full set? Work through the GROUP BY & Aggregation study plan.
Questions people ask
- Can I GROUP BY more than one column?
- Yes. GROUP BY dept_id, EXTRACT(YEAR FROM hire_date) makes one group per department and year combination.
- Does GROUP BY sort the result?
- Not reliably. Add ORDER BY whenever the order matters.