
📎 This article includes 2 downloadable practice files ↓
In this article
Lesson 4 gave one total for the whole table. Reports need totals per something: per region, per product, per month. That’s GROUP BY, the SQL version of a pivot table.
One row per group
SELECT region, COUNT(*) AS customers
FROM customers
GROUP BY region
ORDER BY region;
The database sorts rows into groups by region and calculates COUNT for each.
The rule
Every column in SELECT must either be in GROUP BY or inside an aggregate (COUNT, SUM…). SELECT region, name, COUNT(*) … GROUP BY region doesn’t make sense: which name would it show for North?
More examples
SELECT status, COUNT(*) AS orders FROM orders GROUP BY status ORDER BY orders DESC;
SELECT category, COUNT(*) AS products, ROUND(AVG(price), 0) AS avg_price
FROM products GROUP BY category;
SELECT strftime('%Y-%m', order_date) AS month, COUNT(*) AS orders
FROM orders GROUP BY month ORDER BY month;
strftime('%Y-%m', …) turns a date into “2026-07” in SQLite. (SQL Server: FORMAT(order_date,'yyyy-MM'); PostgreSQL: to_char(order_date,'YYYY-MM').)
Group by two columns
SELECT customer_id, status, COUNT(*) FROM orders GROUP BY customer_id, status;
HAVING: filter the groups
SELECT customer_id, COUNT(*) AS orders
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 28;
| WHERE | HAVING | |
|---|---|---|
| Filters | Rows, before grouping | Groups, after grouping |
| Can use aggregates? | No | Yes |
| Example | WHERE status = ‘Delivered’ | HAVING SUM(qty) > 100 |
Clause order
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...;
Where beginners go wrong
| Mistake | Fix |
|---|---|
| Non-grouped column in SELECT | Add it to GROUP BY or aggregate it |
| WHERE COUNT(*) > 5 | HAVING COUNT(*) > 5 |
| Months in the wrong order | Group by ‘YYYY-MM’, not month names |
Practice
Download shop.db and this lesson’s exercise file below. Open the database in the free DB Browser for SQLite (or sqlite3 shop.db), paste the exercises into the Execute SQL tab and write a query for each task. The expected result is printed under every task; answers are at the bottom of the file.
📎 Practice files for this article
- 📄Practice database (SQLite)shop.db: 12 customers, 10 products and 300 orders (April-September 2026).⬇ DB · 28 KB
- 🗃️Lesson 5 exercisesTasks with the expected result under each, and answers at the bottom.⬇ SQL · 2 KB
Free to use for learning. Files with macros (.bas) are plain text — import them with Alt+F11 → File → Import File, and always test on a copy.
Stuck on a step? Ask a question and the AI answers using this article.