Lesson 2 · Module 1: Practical SQL with SQLite
Summarizing like a pivot table: GROUP BY with COUNT, SUM and AVG
- Summarize a whole table with COUNT, SUM and AVG
- Group rows with GROUP BY, just like pivot table Rows
- Filter groups with HAVING and sort the summary
Why this matters
Every week you probably rebuild the same pivot table: category in Rows, amounts in Values, a sum here, a count there. It works until the file gets huge or you have to redo it every Monday. SQL gives you the same summary as a few lines of text that you save once and rerun whenever the data changes. This lesson turns your pivot table habits into SQL.
Key ideas
- Aggregate functions. COUNT, SUM, AVG, MIN and MAX squeeze many rows into one number, like the totals you type under an Excel column.
COUNT(*)counts every row. - GROUP BY is the Rows area. It splits the rows into piles, one per distinct value, and runs each summary function once per pile. You get one result row per pile. Take this small sales table:
| region | amount |
|---|---|
| North | 10 |
| South | 30 |
| North | 20 |
| South | 50 |
| West | 40 |
SELECT region, COUNT(*), SUM(amount), AVG(amount)
FROM sales
GROUP BY region;
The result has three rows: North (2, 30, 15), South (2, 80, 40), West (1, 40, 40).
- The one rule. Every column after SELECT must either be listed in GROUP BY or sit inside a summary function. In pivot terms, every field is either in Rows or in Values. SQLite will not stop you from breaking this rule. It just shows a value from some row of the group, which can look believable and still be wrong.
- WHERE before, HAVING after. From the lesson: Your first query: SELECT, WHERE and ORDER BY on the orders tableWHERE keeps only the rows that meet a condition, like a filter on a column in Excel.Review that lesson checks single rows before any groups exist. HAVING is a a part of a query that starts with a keyword, such as WHERE or ORDER BY that checks whole groups after they are built, like a pivot value filter.
- Sort the summary. From the lesson: Your first query: SELECT, WHERE and ORDER BY on the orders tableORDER BY sorts the result rows by a column; add DESC to put the largest values first.Review that lesson can sort by a summary, for example
ORDER BY SUM(amount) DESCto put the biggest group first.
Try it before you watch
Using the sales table above, which regions are left if you add HAVING SUM(amount) > 35 to the query?
Show answerHide answer
South (total 80) and West (total 40) stay. North is removed because its total is 30, which is not above 35. HAVING judges each finished group by its summary number, not the single rows inside it.
Words to know
- aggregate function: a function that turns many rows into one number, such as COUNT, SUM or AVG.
- GROUP BY: the part of a query that splits rows into groups by the values of one or more columns.
- HAVING: a filter that keeps or drops whole groups, based on their summary numbers.
- AS: gives a result column a name, like typing a column header.
- ROUND: trims a number to a set number of decimal places, like the Decrease Decimal button.
What to watch for
- Watch the animation where the orders fall into piles. That moment, split first and calculate second, is the whole of GROUP BY.
- Notice where HAVING sits in the query, after GROUP BY, and why WHERE cannot do its job.
- Look closely at the "silent extra column" mistake near the end. The output looks fine, and that is exactly the danger.
Learn it with your tutor on the live board
Key points from the video
Your manager wants totals by category
- Question: "How much did each category sell, and how many orders?"
- In Excel: build a pivot table, drag fields, refresh
- In SQL: one short query you can rerun anytime
- Same idea, far bigger data
What you will learn
- Summarize a whole table with COUNT, SUM and AVG
- Group rows with GROUP BY, just like pivot table Rows
- Filter groups with HAVING and sort the summary
How a pivot table really works
- Take all the rows
- Split them into groups by one column
- Calculate one number per group
- Show one row per group
Summary functions on the whole table
- COUNT(*) counts rows
- SUM adds up, AVG averages
- Result: 8 orders, 232 total, 29 average
SELECT COUNT(*), SUM(amount), AVG(amount)
FROM orders; GROUP BY: totals per category
- GROUP BY category makes one group per category
- Each summary function runs once per group
- Result: one row per category
layout: wide
tables:
orders:
columns: [id, customer, category, amount]
rows: [[1, Ana, Books, 20], [2, Ben, Toys, 35], [3, Ana, Toys, 15], [4, Chen, Books, 40], [5, Ben, Books, 10], [6, Dina, Games, 60], [7, Chen, Toys, 22], [8, Ana, Games, 30]]
query: |
SELECT category,
COUNT(*) AS n_orders,
SUM(amount) AS total,
ROUND(AVG(amount), 2) AS average
FROM orders
GROUP BY category
steps:
- at: "here are our eight orders"
show: tables
- at: "our summary query"
show: query
- at: "fall into three piles"
show: group
- at: "finished summary appears"
show: result The one rule of GROUP BY
- Every column in SELECT must be either:
- listed in GROUP BY, or
- inside a summary function like SUM
- Pivot version: a field is in Rows or in Values
Run it yourself in Python
import sqlite3
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE orders(id, customer, category, amount)")
con.executemany("INSERT INTO orders VALUES (?,?,?,?)", [
(1, "Ana", "Books", 20), (2, "Ben", "Toys", 35),
(3, "Ana", "Toys", 15), (4, "Chen", "Books", 40),
(5, "Ben", "Books", 10), (6, "Dina", "Games", 60),
(7, "Chen", "Toys", 22), (8, "Ana", "Games", 30)])
sql = """
SELECT category, COUNT(*), SUM(amount), ROUND(AVG(amount), 2)
FROM orders
GROUP BY category
ORDER BY SUM(amount) DESC
"""
for row in con.execute(sql):
print(row) HAVING: filter the groups
- WHERE filters rows before grouping
- HAVING filters groups after grouping
- Example: customers with at least 2 orders
layout: wide
tables:
orders:
columns: [id, customer, category, amount]
rows: [[1, Ana, Books, 20], [2, Ben, Toys, 35], [3, Ana, Toys, 15], [4, Chen, Books, 40], [5, Ben, Books, 10], [6, Dina, Games, 60], [7, Chen, Toys, 22], [8, Ana, Games, 30]]
query: |
SELECT customer, COUNT(*) AS n_orders,
SUM(amount) AS total
FROM orders
GROUP BY customer
HAVING COUNT(*) >= 2
ORDER BY total DESC
steps:
- at: "repeat customers"
show: tables
- at: "group by customer this time"
show: query
- at: "four customer piles"
show: group
- at: "Dina drops out"
show: having
- at: "biggest spender first"
show: order
- at: "three loyal customers"
show: result Pivot table fields in SQL
- Rows area = GROUP BY
- Values area = COUNT, SUM, AVG, MIN, MAX
- Two row fields = GROUP BY customer, category
- Report filter = WHERE
- Value filter = HAVING
- Sort = ORDER BY
Common mistake: the silent extra column
- SQLite does not stop you
- It shows the category from one arbitrary row
- Fix: add category to GROUP BY, or remove it
-- Looks fine, but is wrong
SELECT customer, category, SUM(amount)
FROM orders
GROUP BY customer; Recap
- Summary functions squeeze many rows into one number
- GROUP BY gives one row per group, like pivot Rows
- Every selected column: grouped or summarized
- WHERE filters rows, HAVING filters groups
- ORDER BY sorts the summary
Your exercise
- Use the Python program from this lesson
- Group the orders by customer
- Show: number of orders, largest order (MAX), average rounded to 2 decimals
- Keep only customers whose average is above 25
- Sort by average, highest first
Transcript
Your manager wants totals by category. It's Monday morning, and your manager asks a simple question. How much did each product category sell, and how many orders did each one get? In Excel, you'd reach for a pivot table. You'd drag Category into Rows and drag Amount into Values. Then you'd drag Amount into Values a second time and set that copy to Count. That works well, until the data has half a million rows, or you have to rebuild the same pivot every week. In this lesson you'll learn the SQL version of a pivot table. It's a short query you write once, save, and rerun whenever the numbers change. It works on our online shop's orders table in SQLite.
What you will learn. Here's the plan for the next few minutes. First, you'll learn three summary functions, COUNT, SUM and AVG, and use them on the whole orders table. Second, you'll learn GROUP BY, which splits the rows into groups, exactly like the Rows area of a pivot table does. Third, you'll learn how to keep only some of those groups with a clause called HAVING, and how to sort the finished summary so the biggest numbers come first. By the end, you'll be able to answer the Monday morning question in about six lines of SQL.
How a pivot table really works. Before any SQL, let's look at what a pivot table actually does behind the scenes, because SQL does exactly the same four steps. It starts with all the rows of your data. Then it splits those rows into piles, one pile for each distinct value in the column you picked, say one pile for Books, one for Toys, one for Games. Next, it calculates a number for each pile, like a sum or a count. Finally, it shows you one row per pile. Keep this picture in mind: split, then calculate, then one row per group. That's all GROUP BY is.
Summary functions on the whole table. Let's start without any groups. COUNT, SUM and AVG are called aggregate functions. Aggregate just means they squeeze many rows into one number, like the total at the bottom of an Excel column. COUNT star counts rows, the star meaning count every row. SUM adds up a column, and AVG works out the average. Our small orders table has eight orders. Run this query and you get one single row back: eight orders, two hundred thirty two in total sales, and an average order of twenty nine. That's the same as typing COUNTA, SUM and AVERAGE formulas under your column, but in one line.
GROUP BY: totals per category. Now let's add groups. Here are our eight orders, each with a customer, a category and an amount. Watch our summary query. It picks the category column, then counts, sums and averages the amount. AS just gives each result column a name, like a column header. ROUND trims the average to two decimal places. The new line at the bottom, GROUP BY category, is the pivot table's Rows area. The rows fall into three piles: Books, Toys and Games. Then each function runs once per pile. The finished summary appears with one row per category. Books has three orders totalling seventy. Toys has three orders totalling seventy two. Games has just two orders, but they total ninety, with an average of forty five.
The one rule of GROUP BY. GROUP BY has one rule you should never break. Every column you list after SELECT must be either in the GROUP BY list, or wrapped inside a summary function. Think about the pivot table again. Every field you use sits either in the Rows area or in the Values area. Category was in GROUP BY, so it's fine to show it. Amount was inside SUM and AVG, so that's fine too. But what about customer? A category like Books has three different customers, so there's no single customer name to show on the Books row. Some databases refuse such a query outright. SQLite runs it anyway, as you'll see in the common mistake later, so the rule is yours to keep.
Run it yourself in Python. Here's a nice shortcut: Python comes with SQLite built in, so you can try every query from this lesson without installing anything. This small program creates the orders table in memory, fills in our eight orders, and runs the category summary. You met ROUND already: it trims the average to two decimal places, just like the Decrease Decimal button in Excel. The new addition is ORDER BY SUM of amount, descending, which sorts the summary so the biggest category comes first. Games comes out on top with ninety, then Toys with seventy two, then Books with seventy.
HAVING: filter the groups. Now marketing wants to know about repeat customers. Let's group by customer this time. That gives us four customer piles: Ana, Ben, Chen and Dina. We only want customers with at least two orders. You can't use WHERE for that, because WHERE checks single rows before any groups exist. Instead we use HAVING, which checks each group after it's built. HAVING count star is at least two, so Dina drops out with her single order. Then ORDER BY total, descending, puts the biggest spender first. We end with three loyal customers: Ana with sixty five, Chen with sixty two, and Ben with forty five.
Pivot table fields in SQL. Let's put the two worlds side by side, so you can translate any pivot table you already know. The Rows area becomes GROUP BY. The Values area becomes your summary functions: COUNT, SUM, AVG, and also MIN and MAX for the smallest and largest value. The report filter at the top of a pivot, like showing only last month, becomes WHERE. A value filter, like showing only customers above a certain total, becomes HAVING. Sorting becomes ORDER BY. Want two row fields, like customer and then category? Just list both columns in GROUP BY, separated by a comma.
Common mistake: the silent extra column. Here's the trap that catches almost every beginner. This query adds category to the SELECT list, but groups only by customer. Many databases would refuse to run it. SQLite doesn't. It quietly runs and shows a category for each customer, taken from whichever of that customer's rows it happened to look at. Ana bought Books, Toys and Games, yet her row shows just one of them, and it looks perfectly believable in a report. There are two fixes. Add category to GROUP BY, so you get one row per customer and category. Or remove category from SELECT.
Recap. Let's recap. First, summary functions like COUNT, SUM and AVG squeeze many rows into a single number, just like totals under an Excel column. Second, GROUP BY splits the rows into groups and gives you one row per group, exactly like the Rows area of a pivot table, and every column you select must be grouped or summarized. Third, WHERE filters individual rows before grouping, while HAVING filters whole groups after grouping, and ORDER BY sorts the finished summary. With these ideas, you can already rebuild most of the pivot tables you make each week.
Your exercise. Now it's your turn. Take the Python program from this lesson and change only the query. Group the orders by customer. For each customer, show the number of orders, their largest order using MAX, and their average order rounded to two decimals. Then keep only customers whose average order is above twenty five, and sort the result by that average, highest first. Hint: the average filter needs HAVING, not WHERE. Check your answer by hand against the eight orders. In the next lesson, we'll combine tables, the SQL way of doing a VLOOKUP.
Explore the animations
The animations from the video, exact and live: play them, pause, drag the slider or jump to a step at your own pace.
Loading the animation...
Loading the animation...
Python runs in your browser, nothing to install. The first run downloads Python (about 20 MB, then cached).
1 Category summary like a pivot table
Complete the query in sql so that it returns, for each category, the category, the number of orders, the total amount and the average amount rounded to 2 decimals. Sort the result by total, highest first. The result is stored in rows.
2 Customers with a high average order
Change the query in sql so it groups the orders by customer and shows the customer, the number of orders, the largest order (MAX) and the average order rounded to 2 decimals. Keep only customers whose average is above 25, and sort by that average, highest first. The result is stored in rows.
Answer every question, then check. 70% passes; you can retry as often as you like.
Lesson summary
- Reading
- Video watched
- Exercises
- Quiz passed (70%)
- COUNT(*) counts rows
- SUM adds up, AVG averages
- Result: 8 orders, 232 total, 29 average