Tier 2 · Core SQL

GROUP BY in SQL

By Owen Middleton · Updated September 2026 · Examples run on PostgreSQL 17

What is GROUP BY in SQL?

GROUP BY divides your result into groups and runs an aggregate function on each group separately.

You're building a sales report and the data is already in SQL. You don't want one total. You want revenue broken down by order status. How much came from delivered orders? How much is still pending? That kind of breakdown is exactly what GROUP BY is for.

Without GROUP BY, an aggregate function like COUNT(*) or SUM() collapses the whole table into a single number. GROUP BY changes that: instead of one number for everything, you get one number per group.

How do you write a GROUP BY query in SQL?

Think of it like sorting a stack of receipts into piles before you start counting. One pile per status, one pile per customer. Once sorted, each pile gets its own total. Here's what that looks like:

SELECT status, COUNT(*) AS order_count
FROM orders
GROUP BY status

SQL partitions the orders table into groups, one per distinct status value, then counts the rows in each group. The result has one row per status, not one row per order.

You can use any aggregate function alongside GROUP BY. Revenue per status instead of a count:

SELECT status, SUM(total_amount) AS total_revenue
FROM orders
GROUP BY status

The column you're grouping by appears in SELECT and in GROUP BY. That's the pattern.

Can you use WHERE with GROUP BY in SQL?

GROUP BY works with WHERE. SQL filters first, then groups and aggregates what survives:

SELECT customer_id, COUNT(*) AS delivered_order_count
FROM orders
WHERE status = 'delivered'
GROUP BY customer_id

Only delivered orders reach the grouping step. The result shows a count per customer, but only for their delivered orders.

Why must every SELECT column appear in GROUP BY?

The one thing that trips people up: every column in SELECT must either appear in the GROUP BY clause or be wrapped in an aggregate function.

When you group by status, a single status = 'delivered' group can contain orders from dozens of different customers. SQL has no basis for deciding which customer_id to show you, so it refuses the query. That's the right behavior:

SELECT customer_id, status, COUNT(*)
FROM orders
GROUP BY status

The error identifies exactly which column caused the problem. Fix it by adding customer_id to GROUP BY, giving you one row per customer-status pair, or by dropping customer_id from SELECT if you don't need it.

Check your understanding

You write: SELECT category_id, name, COUNT(*) FROM products GROUP BY category_id. What happens?

Practice GROUP BY in SQL

Practice · easy ecommerce · Brightlane

Brightlane's fulfilment director is reviewing pipeline health ahead of the monthly board report.

Write a query to return the number of orders in each status.

Assumptions:

  • The orders table contains every order Brightlane has processed.
  • The status column has a small handful of values; the result will have one row per status.

Output:

  • One row per status value, with columns status and order_count.
Schema · ecommerce5 tables? = nullable
categories
idinteger
nametext
parent_id?integer
products
idinteger
nametext
category_id?integer
pricenumeric
stock_qtyinteger
attributes?jsonb
order_items
idinteger
order_id?integer
product_id?integer
quantityinteger
unit_pricenumeric
customers
idinteger
nametext
emailtext
city?text
countrytext
created_attimestamptz
is_activeboolean
orders
idinteger
customer_id?integer
ordered_attimestamptz
statustext
total_amountnumeric

Run previews · Check grades

Write a query, then run it to see results here.

Worked solution

The full breakdown walks through the shape, each clause, why this approach beats the alternatives, and the trap to avoid.

See the full worked solution
In the game

Station Zero, our free browser SQL game, teaches this concept inside a story. No signup.

aggregate the station logs in Station Zero

9 GROUP BY practice problems

Start learning to practice all 9 GROUP BY problems, with instant grading and mastery tracking.

Deeper guides on GROUP BY

Common questions about GROUP BY

Does GROUP BY sort the result?

Not reliably. Grouped output often arrives in an order that looks deliberate, but nothing promises it and it can change with the data or the plan. Add ORDER BY whenever the sequence is part of the answer.

Can you group by more than one column?

Yes. List them after GROUP BY separated by commas and you get one row per distinct combination, so grouping by country and status gives a row for each pairing that actually occurs rather than for every pairing that could.

What happens to NULLs in a grouped column?

They collect into a single group of their own. Every row missing a value ends up in one output row where that column is NULL, which is easy to skim past when you are reading a long result and expecting only real categories.

Can you use GROUP BY without an aggregate?

Yes, and it behaves like SELECT DISTINCT: one row per group and no summary. It is valid SQL, though saying DISTINCT usually states the intent more plainly than grouping and then aggregating nothing.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.