What You Will Learn
- What GROUP BY does and why it is so important
- How to group by one column and by multiple columns
- The HAVING clause — filtering groups (not rows)
- The exact order SQL evaluates clauses
Why This Topic Matters
GROUP BY is the SQL version of an Excel pivot table (lesson 15) or a pandas groupby() (lesson 26). It is the foundation of every business report: "revenue by city", "orders by month", "top customers by spend". If you only learn one intermediate SQL feature, make it this one.
Sample Sales Data
A more realistic sales table with 12 rows:
| order_id | date | product | region | sales |
|---|---|---|---|---|
| 1 | 2026-08-01 | Laptop | South | 50000 |
| 2 | 2026-08-02 | Mouse | South | 500 |
| 3 | 2026-08-03 | Laptop | North | 60000 |
| 4 | 2026-08-04 | Mouse | South | 500 |
| 5 | 2026-08-05 | Keyboard | North | 1500 |
| 6 | 2026-08-06 | Laptop | South | 55000 |
| 7 | 2026-08-07 | Mouse | North | 500 |
| 8 | 2026-08-08 | Keyboard | South | 1500 |
| 9 | 2026-08-09 | Laptop | North | 52000 |
| 10 | 2026-08-10 | Mouse | South | 500 |
| 11 | 2026-08-11 | Keyboard | North | 1500 |
| 12 | 2026-08-12 | Laptop | South | 51000 |
The Question GROUP BY Answers
Without GROUP BY, your aggregations collapse the whole table into one number: SUM(sales) returns ₹2,73,000 — total of everything. Useful, but limited.
With GROUP BY, you can ask: "What is the SUM of sales for each region separately?" Or: "For each product, what is the total sales?"
Your First GROUP BY
SELECT region, SUM(sales) AS total_sales
FROM sales
GROUP BY region;
Result:
| region | total_sales |
|---|---|
| North | 115500 |
| South | 157500 |
What the database actually does
- Sorts the rows by
region— North rows together, South rows together. - For each group, computes
SUM(sales). - Returns one row per group.
The output has 2 rows because there are 2 unique regions.
The Rule: Every Non-Aggregated Column Must Be in GROUP BY
This is the rule that catches every beginner:
If a column appears in SELECT and is not wrapped in an aggregate function (SUM, COUNT, AVG, etc.), it must appear in GROUP BY.
So this is invalid:
SELECT region, product, SUM(sales) -- product is not aggregated
FROM sales
GROUP BY region; -- and not in GROUP BY either
Most databases reject it. (SQLite allows it but returns an arbitrary product for each region — silently wrong.) The fix is to add product to GROUP BY:
GROUP BY Multiple Columns
SELECT region, product, SUM(sales) AS total_sales
FROM sales
GROUP BY region, product
ORDER BY region, total_sales DESC;
Result:
| region | product | total_sales |
|---|---|---|
| North | Laptop | 112000 |
| North | Keyboard | 3000 |
| North | Mouse | 500 |
| South | Laptop | 156000 |
| South | Keyboard | 1500 |
| South | Mouse | 1500 |
Now you see sales broken down by region AND product. The result has 6 rows because there are 2 regions × 3 products = 6 combinations (and all 6 actually appear in the data).
Grouping by Date Parts
Grouping by raw dates gives one row per day. To roll up to months, use date functions:
-- PostgreSQL / SQLite (strftime):
SELECT strftime('%Y-%m', date) AS month, SUM(sales) AS monthly_sales
FROM sales
GROUP BY strftime('%Y-%m', date)
ORDER BY month;
-- MySQL:
SELECT DATE_FORMAT(date, '%Y-%m') AS month, SUM(sales)
FROM sales
GROUP BY DATE_FORMAT(date, '%Y-%m');
-- SQL Server:
SELECT FORMAT(date, 'yyyy-MM') AS month, SUM(sales)
FROM sales
GROUP BY FORMAT(date, 'yyyy-MM');
HAVING — Filtering Groups
WHERE filters rows before grouping. HAVING filters groups after grouping.
SELECT product, SUM(sales) AS total_sales
FROM sales
GROUP BY product
HAVING SUM(sales) > 50000;
Result:
| product | total_sales |
|---|---|
| Laptop | 268000 |
Only Laptop had total sales above ₹50,000. Keyboard (₹4,500) and Mouse (₹2,000) are filtered out.
WHERE vs HAVING
| Clause | What it filters | When it runs |
|---|---|---|
| WHERE | Individual rows | Before grouping |
| HAVING | Groups (after aggregation) | After grouping |
Combining both
SELECT product, SUM(sales) AS total_sales
FROM sales
WHERE region = 'South' -- only South rows enter the grouping
GROUP BY product
HAVING SUM(sales) > 1000; -- keep only products with > 1000 in sales
The Full Clause Order
SQL evaluates clauses in this order (even though you write them differently):
- FROM — load the source table(s)
- WHERE — filter rows
- GROUP BY — group surviving rows
- HAVING — filter groups
- SELECT — pick columns / compute aggregates
- ORDER BY — sort the result
- LIMIT — pick top N
Memorize this order. Most SQL bugs trace back to misunderstanding it — e.g., trying to use a column alias from SELECT inside WHERE (not allowed, because WHERE runs before SELECT).
Common Mistakes
- Selecting a column not in GROUP BY. Violates the rule above. Add it to GROUP BY or wrap it in an aggregate.
- Using WHERE to filter on an aggregate.
WHERE SUM(sales) > 1000is invalid. Use HAVING. - Forgetting that GROUP BY changes row count. Output rows = number of groups, not number of input rows. A common surprise.
- Assuming GROUP BY sorts the output. It does not, formally. Some databases sort by the GROUP BY columns as a side effect, but you should always add ORDER BY explicitly.
- Counting the wrong thing.
SELECT product, COUNT(*)counts rows per product.SELECT product, COUNT(sales)counts non-NULL sales values per product. Usually you want COUNT(*).
Practical Exercise (15 minutes)
Using the 12-row sales table above, write queries to compute:
- Total sales by region.
- Total sales by product.
- Number of orders by region.
- Total sales by region AND product.
- Products whose total sales exceed ₹50,000.
- Regions where the average sale is above ₹20,000.
- Top product by sales in the South region.
Mini Challenge
Write a query that returns, for each region, the product with the highest total sales. (This actually requires a subquery or window function — see lesson 23. For now, see how far you can get with just GROUP BY and ORDER BY. Can you at least produce one row per region that shows the top product? Hint: use a subquery per region.)
Key Takeaways
- GROUP BY groups rows by a column; aggregates run within each group.
- Every non-aggregated column in SELECT must appear in GROUP BY.
- WHERE filters rows before grouping; HAVING filters groups after.
- Clause order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.
- Always add ORDER BY — GROUP BY does not guarantee sorted output.
Previously learned: Lesson 20 covered the five aggregates.
Today: You learned to apply those aggregates per group — the most-used pattern in business SQL.
Next: Lesson 22 covers JOINs — how to GROUP BY across multiple tables.
FAQ
Can I GROUP BY a column I do not select?
Yes. SELECT SUM(sales) FROM sales GROUP BY region is valid — it returns one sum per region but does not show which region each sum belongs to. Usually useless, but legal.
Why does GROUP BY not sort the output automatically?
Because the SQL standard does not require it. Some databases (older MySQL versions) sorted as a side effect, but you should not rely on it. Always use ORDER BY for guaranteed ordering.
Comments
Comments
Post a Comment