Skip to content

Beginner: asking questions · Aggregation: GROUP BY and HAVING · topic 17 of 62 · 35 min

GROUP BY

Explanation

GROUP BY splits the rows into groups that share the same value, and the aggregates then run once per group, giving one result row per group: students per city, revenue per order, average salary per department. Every column in the SELECT list must either be in GROUP BY or be inside an aggregate.

One row per group

The last topic gave one number for the whole table. Usually you want that number for each group: how many students in each city, the total of each order, the average salary in each department. GROUP BY column puts rows with the same value in that column into one group, then each aggregate runs separately for every group.

Think of it like this: Think of counting votes after an election. First the ballots are sorted into piles, one pile per candidate (GROUP BY candidate). Then each pile is counted (COUNT(*)). The result has one line per candidate, not one line per ballot.
sql
SELECT city, COUNT(*) AS students
FROM students
GROUP BY city;
-- Chennai   | 1
-- Delhi     | 2
-- Hyderabad | 1
-- Mumbai    | 1
-- Pune      | 3
Eight student rows become five city rows.

Totals per order

order_items has one row per product in an order. To get each order's value, group the lines by order_id and add up qty × price.

sql
SELECT order_id, SUM(qty * price) AS order_total
FROM order_items
GROUP BY order_id;
-- 101 | 1780
-- 102 | 1999
-- 103 | 2248   ... 8 orders

Several aggregates at once, and the NULL group

sql
SELECT dept_id, COUNT(*) AS employees, ROUND(AVG(salary), 2) AS avg_salary
FROM employees
GROUP BY dept_id;
-- NULL | 1 | 45000
-- 1    | 4 | 140000
-- 2    | 3 | 80666.67
-- 3    | 2 | 86500
-- 4    | 1 | 80000
Deepak has no department, and all NULLs form one group of their own, just as with DISTINCT.

The golden rule

After grouping, each result row stands for a whole group. A column in GROUP BY has one value per group, so it can be shown. A column inside an aggregate becomes one value per group, so it can be shown. Any other column has many values in the group, and SQL cannot know which one to show. So: every selected column is either grouped or aggregated.

sql
SELECT city, name, MAX(marks) FROM students GROUP BY city;   -- name is neither!
PostgreSQL: "column students.name must appear in the GROUP BY clause or be used in an aggregate function".
Careful: SQLite (and MySQL with ONLY_FULL_GROUP_BY switched off) runs that query anyway and picks a name for you. SQLite has a special case where, with MAX or MIN, it takes the name from the row with the maximum, but in general the value is not something you chose. MySQL 5.7 and later switch ONLY_FULL_GROUP_BY on by default and reject it. Follow the rule even in SQLite, so your SQL is correct everywhere.

Grouping by more than one column

GROUP BY city, course makes one group for each different (city, course) pair. Pune with BCA, Pune with BCom and Pune with BSc are three groups. The more columns, the smaller the groups.

sql
SELECT category, COUNT(*) AS products, MIN(price) AS cheapest, MAX(price) AS dearest
FROM products
GROUP BY category
ORDER BY category;
-- Electronics | 2 | 1299 | 1999
-- Fashion     | 2 | 899  | 2499
-- Grocery     | 2 | 160  | 650
-- Kitchen     | 2 | 450  | 1450

Ordering groups

GROUP BY does not promise any order (SQLite often returns groups sorted, but that is a side effect). Add ORDER BY. You can sort by a grouped column, by an aggregate, or by its alias: ORDER BY students DESC gives the biggest city first. With ties, add a tie-breaker such as ORDER BY students DESC, city.

Where GROUP BY sits in a query

Written order: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. WHERE comes before GROUP BY, so it filters rows before they are put into groups. The next topic adds HAVING, which filters whole groups.

Common mistakes

  • Selecting a column that is neither grouped nor aggregated. Add it to GROUP BY, or wrap it in an aggregate.
  • Grouping by too many columns (for example by id), so every group has one row and nothing is summarised.
  • Expecting groups to come out sorted. Always write ORDER BY.
  • Forgetting the NULL group. Rows with a NULL key form their own group; filter them with WHERE col IS NOT NULL or label them with COALESCE.
  • Grouping by a display name that is not unique, so two different customers called "Ravi Kumar" merge into one group. Group by the id, and show the name alongside (module 5 shows how with a JOIN).

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

GROUP BY · 1/7

GROUP BY

Most reports want a number for each group: students per city, total per order, average salary per department.

Prefer a video by a teacher? Find free videos on “sql group by tutorial count per group beginners” on YouTube ↗

In real life: Sales per category for a weekly review

Retail chains review sales by category every week: how many items, how much revenue, which category is growing. The query behind such a table is a GROUP BY category with COUNT and SUM. The same pattern gives patients per ward in a hospital, tickets per train in a booking system, or complaints per district on a government portal.

sql
SELECT category,
       COUNT(*)      AS products,
       SUM(stock)    AS units
FROM products
GROUP BY category
ORDER BY units DESC;
Grocery 115 units, Fashion 45, Electronics 25, Kitchen 20.

Practice (0/4 done)

Task 1 · Code · SQL

How many students live in each city? Return city and the number of students, named students.

Task 2 · Code · SQL

Work out the value of every order. From order_items, return order_id and the sum of qty * price, named order_total.

Task 3 · Code · SQL

For each dept_id in employees (COMPANY), return dept_id, the number of employees named employees, and the average salary rounded to 2 decimal places named avg_salary. Keep the group of people with no department.

Task 4 · Code · SQL

For each product category, return category, the number of products named products, the lowest price named cheapest and the highest price named dearest, sorted by category A to Z.

Quick quiz

Question 1

1.students has 8 rows in 5 different cities. How many rows does SELECT city, COUNT(*) FROM students GROUP BY city return?

Question 2

2.Which query follows the GROUP BY rule?

Question 3

3.Two employees have dept_id NULL. After GROUP BY dept_id, where are they?

Question 4

4.What does GROUP BY city, course group together?

Question 5

5.Your GROUP BY results came out sorted by city in SQLite. Is that guaranteed?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

students has 8 rows in 5 different cities. How many rows does SELECT city, COUNT(*) FROM students GROUP BY city return?

Answer: 5

GROUP BY gives one row per group, and there are 5 cities.

Which query follows the GROUP BY rule?

Answer: SELECT dept_id, AVG(salary) FROM employees GROUP BY dept_id

dept_id is grouped and salary is aggregated; name is neither in the other options.

Two employees have dept_id NULL. After GROUP BY dept_id, where are they?

Answer: In one NULL group together

All NULLs fall into a single group.

What does GROUP BY city, course group together?

Answer: Rows with the same city AND the same course

Each different combination of the grouped columns is one group.

Your GROUP BY results came out sorted by city in SQLite. Is that guaranteed?

Answer: No; add ORDER BY if the order matters

Any order without ORDER BY is a side effect that can change.

You want to ignore cancelled orders before adding up totals per city. Where does status <> 'cancelled' go?Related

Answer: In WHERE

It is a test on single rows, so it belongs in WHERE, which runs before grouping.

From GROUP BY and HAVING

What does SELECT city, SUM(amount) FROM orders GROUP BY city return?Related

Answer: One row per city with its total

GROUP BY city makes one group per city, and SUM runs on each group.

From Counting and totals: aggregate functions

SELECT city, COUNT(*) AS n FROM students GROUP BY city HAVING n > 1 works in SQLite. What happens in PostgreSQL?Related

Answer: It fails, because a SELECT alias is not visible in HAVING

HAVING runs before SELECT in standard SQL. Write HAVING COUNT(*) > 1.

From HAVING versus WHERE

employees has 11 rows. How many rows does SELECT name, AVG(salary) OVER (PARTITION BY dept_id) FROM employees return?Related

Answer: 11

A window function never removes rows; each of the 11 rows gets its department average.

From OVER and PARTITION BY

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Aggregation: GROUP BY and HAVING” before its exam: HAVING versus WHERE, Conditional aggregation and string aggregation, Writing reports with aggregation.
  2. Module 4 exam: Aggregation, GROUP BY and HAVING · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, JOINs. 62 topics left in the course before the final exam.

← Previous: Aggregate functions