SQL Lab: groups¶
Exercise sql-lab-groups-annex.pdf · PDF
How many fire Pokémon are there? You know how to find out: one query with WHERE type_1 = 'fire', and the answer is 59. Now do water. Then grass. Then the other fifteen types.
Eighteen queries for one small question. Nobody wants that. And the questions people really ask about data are almost never "show me the cards". They are how many, how much, what is the average, which group wins. In the last lesson every answer was a list of cards. In this one, the answer is a number about many cards — and then the same number for every group at once.
What you will learn
- Summarise a whole table in one row with the aggregate functions
COUNT,SUM,AVG,MINandMAX - Explain why
COUNT(*)andCOUNT(column)can give different numbers on the same table - Split a table into groups with
GROUP BYand compute one figure per group - Keep only some groups with
HAVING - Defuse the trap of the day:
WHEREandHAVINGare not the same thing - Read the error
must appear in the GROUP BY clauseand fix the query
One number for the whole box¶
An aggregate function reads many rows and returns one value.
We keep the shoebox from SQL Lab: filters: the table pokemon is a box of 949 trading cards, a row is a card, a column is a line printed on it. An aggregate function reads a whole stack of cards and says one number.
| Function | With the shoebox | On pokemon |
|---|---|---|
COUNT(*) |
How many cards | 949 |
MIN(x), MAX(x) |
The smallest and the biggest value printed on that line | attack: 5 and 190 |
SUM(x) |
Add that line up over all the cards | attack: 75419 |
AVG(x) |
The sum divided by how many: the average | attack: 79.47... |
Open SQL → SQL Lab (database Dades classe, schema public) and run:
SELECT MIN(attack), MAX(attack), SUM(attack), AVG(attack) FROM pokemon;
| min | max | sum | avg |
|---|---|---|---|
| 5 | 190 | 75419 | 79.4720758693361433 |
949 cards went in. One row came out. That is the mark of an aggregate: however big the table, you get a single line.
The average is correct and unreadable. Two small tools fix that:
SELECT ROUND(AVG(attack), 1) AS avg_attack FROM pokemon;
| avg_attack |
|---|
| 79.5 |
ROUND(value, 1)rounds to one decimal.ROUND(value)rounds to a whole number.ASgives the result column a name you choose. That name is called an alias.
An aggregate works together with WHERE just as you would expect: first the cards are taken out, then they are counted. SELECT COUNT(*) FROM pokemon WHERE type_1 = 'fire' returns 59.
COUNT(*) is not COUNT(column)¶
Same table, three counts:
SELECT COUNT(*) AS total,
COUNT(type_2) AS with_second_type,
COUNT(DISTINCT type_1) AS types
FROM pokemon;
| total | with_second_type | types |
|---|---|---|
| 949 | 510 | 18 |
949 and 510, from the same box. Who is lying? Nobody. They answer different questions:
| You write | It counts | Result |
|---|---|---|
COUNT(*) |
Cards | 949 |
COUNT(type_2) |
Cards where the line type_2 is not blank |
510 |
COUNT(DISTINCT type_1) |
Different values on the line type_1 |
18 |
And 949 − 510 = 439: the single-type Pokémon you found with IS NULL in the last lesson.
Careful: aggregates skip blanks
COUNT(column), SUM, AVG, MIN and MAX all ignore NULL. That is usually what you want: an average of the cards that have a value. But it means an average may be computed over fewer rows than you think. When a number matters, put COUNT(*) and COUNT(column) side by side and see how many cards really took part.
GROUP BY: make piles¶
GROUP BYsplits the rows into groups, one group per different value of a column, and returns one result row per group.
Take a deck of playing cards and deal it into piles, one pile per suit. How many piles? One per different suit. Now count each pile. How many numbers do you get? One per pile.
That is all GROUP BY does: one pile per different value, one result row per pile.
SELECT type_1, COUNT(*) AS n
FROM pokemon
GROUP BY type_1
ORDER BY n DESC;
18 rows. The first three and the last one:
| type_1 | n |
|---|---|
| water | 126 |
| normal | 111 |
| grass | 84 |
| ... | ... |
| flying | 4 |
The eighteen queries from the top of the page, in four lines. Remember SELECT DISTINCT type_1? It also returned 18 rows. Same piles; now each pile also gets counted.
You can ask several figures of each pile at once:
SELECT type_1, COUNT(*) AS n, ROUND(AVG(attack)) AS avg_attack
FROM pokemon
GROUP BY type_1
ORDER BY avg_attack DESC;
| type_1 | n | avg_attack |
|---|---|---|
| dragon | 39 | 109 |
| fighting | 31 | 99 |
| ground | 36 | 96 |
| steel | 30 | 93 |
| ... | ... | ... |
flowchart LR
A["<b>The box</b><br>949 cards"] --> B["<b>GROUP BY type_1</b><br>deal them into piles"]
B --> P1["water<br>126 cards"]
B --> P2["normal<br>111 cards"]
B --> P3["...16 more piles"]
P1 --> R["<b>Result</b><br>18 rows:<br>one per pile"]
P2 --> R
P3 --> R
Careful: GROUP BY does not sort
The piles come out in whatever order the database finds convenient. If you want the biggest pile first, say so with ORDER BY. And blanks are not thrown away: rows with a NULL in the grouping column form their own pile. You will meet that pile in "Try it".
The error everybody meets¶
Add the name of the Pokémon to the query above:
SELECT type_1, pokemon, COUNT(*) FROM pokemon GROUP BY type_1;
ERROR: column "pokemon.pokemon" must appear in the GROUP BY clause
or be used in an aggregate function
Read it with the piles in front of you. The water pile has 126 cards. Which one name should the database print for the whole pile? It cannot choose, so it refuses.
The rule: in a grouped query, every column in SELECT is either what you made the piles by, or inside an aggregate function.
HAVING: throw away whole piles¶
HAVINGkeeps only the groups for which a condition is true. It is a filter for piles, not for cards.
Look again at the last row of the first result: flying, 4 cards. An average of four cards is not worth much. Keep only the big piles:
SELECT type_1, COUNT(*) AS n, ROUND(AVG(attack)) AS avg_attack
FROM pokemon
GROUP BY type_1
HAVING COUNT(*) >= 50
ORDER BY avg_attack DESC;
| type_1 | n | avg_attack |
|---|---|---|
| rock | 65 | 90 |
| fire | 59 | 84 |
| normal | 111 | 76 |
| grass | 84 | 75 |
| water | 126 | 75 |
| psychic | 64 | 73 |
| bug | 79 | 72 |
| electric | 61 | 68 |
Eight piles are left. dragon, the winner a moment ago, is gone: it only has 39 cards.
Careful: no alias in HAVING
It is tempting to write HAVING n >= 50, using the alias from SELECT. PostgreSQL refuses: it does not know n yet at that point. Repeat the function: HAVING COUNT(*) >= 50. In ORDER BY the alias does work, as you saw above.
WHERE or HAVING? Cards or piles¶
This is the idea people mix up most. Both lines filter. They filter different things, at different moments.
WHERE |
HAVING |
|
|---|---|---|
| It filters | Cards (rows) | Piles (groups) |
| When | Before the piles are made | After the piles are counted |
| The condition is about | One card: type_2 IS NULL, attack > 100 |
A whole pile: COUNT(*) >= 50, AVG(speed) > 80 |
| Can it use an aggregate? | No | Yes |
That is why this fails:
SELECT type_1, COUNT(*) FROM pokemon WHERE COUNT(*) >= 50 GROUP BY type_1;
ERROR: aggregate functions are not allowed in WHERE
When WHERE runs, there are no piles yet. There is nothing to count.
A query can use both. Average attack per type, only types with at least 50 Pokémon, ignoring the Mega evolutions:
SELECT type_1, COUNT(*) AS n, ROUND(AVG(attack)) AS avg_attack
FROM pokemon
WHERE pokemon NOT LIKE '%-mega%'
GROUP BY type_1
HAVING COUNT(*) >= 50
ORDER BY avg_attack DESC;
| type_1 | n | avg_attack |
|---|---|---|
| rock | 62 | 87 |
| fire | 55 | 81 |
| normal | 107 | 75 |
| ... | ... | ... |
"Is not a Mega" is a question about each card: WHERE. "Has at least 50" is a question about each pile: HAVING. Compare with the previous table: rock dropped from 90 to 87 and fire from 84 to 81. Mega evolutions are very strong, so they were pulling the averages up.
The real order of a query¶
You now know seven lines. You write them in one order. The database executes them in another — and that order explains every rule on this page.
| You write | Executed | With the shoebox |
|---|---|---|
SELECT |
5th | Read the lines you asked for, and compute the aggregates |
FROM |
1st | Open the box |
WHERE |
2nd | Take cards out, one by one |
GROUP BY |
3rd | Make piles with the cards that are left |
HAVING |
4th | Throw away whole piles |
ORDER BY |
6th | Sort the result rows |
LIMIT |
7th | Keep the first ones |
flowchart LR
F["<b>1 · FROM</b><br>open the box"] --> W["<b>2 · WHERE</b><br>take cards out"]
W --> G["<b>3 · GROUP BY</b><br>make piles"]
G --> H["<b>4 · HAVING</b><br>drop whole piles"]
H --> S["<b>5 · SELECT</b><br>read and compute"]
S --> O["<b>6 · ORDER BY</b><br>sort"]
O --> L["<b>7 · LIMIT</b><br>keep the top"]
WHERE runs before the piles exist, so it cannot count them. HAVING runs before SELECT, so it does not know your aliases. ORDER BY runs after SELECT, so it does.
Try it: which type wins?¶
One query per question, in SQL Lab. Write yours first, then compare.
| N. | Question |
|---|---|
| 1 | Lowest, highest and average hp of the whole table (average with one decimal), in one query. |
| 2 | Average speed per first type, fastest first. Which are the top three? |
| 3 | The same, but only types with at least 30 Pokémon and an average speed above 80. Who drops out? |
| 4 | How many Pokémon per generation_id? How many rows come out, and what is odd about one of them? |
| 5 | For each first type: how many Pokémon, and how many of them have a second type. One query. |
Solutions
1 — SELECT MIN(hp), MAX(hp), ROUND(AVG(hp), 1) FROM pokemon; → 1, 255 and 69.0 (SQL Lab's grid shows it as 69).
2 — SELECT type_1, ROUND(AVG(speed)) AS avg_speed FROM pokemon GROUP BY type_1 ORDER BY avg_speed DESC; → flying 103, electric 87, dragon 83.
3 — Add HAVING COUNT(*) >= 30 AND AVG(speed) > 80 after the GROUP BY line → electric 87, dragon 83, psychic 81. flying drops out: it was first, but with only 4 cards.
4 — SELECT generation_id, COUNT(*) AS n FROM pokemon GROUP BY generation_id ORDER BY generation_id; → 8 rows, not 7: 151, 100, 135, 107, 156, 72 and 81 for generations 1 to 7, and a pile with a blank generation_id and 147 cards.
5 — SELECT type_1, COUNT(*) AS n, COUNT(type_2) AS with_second FROM pokemon GROUP BY type_1 ORDER BY n DESC; → water 126 and 60, normal 111 and 44, grass 84 and 45...
Question 4 leaves two loose ends. Who are the 147 cards with no generation? And what is generation 5 — which region, which year? The table has a number, but not what it means.
Key takeaways¶
- An aggregate function (
COUNT,SUM,AVG,MIN,MAX) turns many rows into one value.ROUNDtidies it,ASnames it with an alias. COUNT(*)counts rows;COUNT(column)counts the rows where that column is not blank. Aggregates skipNULL.GROUP BYmakes one pile per different value and returns one row per pile. It does not sort, and blanks form their own pile.- In a grouped query, every column in
SELECTis a pile key or sits inside an aggregate. WHEREfilters cards before the piles are made;HAVINGfilters piles after they are counted.- The database executes
FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT.
Quick check¶
1. How many rows does SELECT MAX(attack), AVG(attack) FROM pokemon; return?
- 949, one per Pokémon
- 2, one per function
- 1
- 18, one per type
An aggregate function reads many rows and returns one value. With no GROUP BY, the whole table is a single pile, so the result is one row — here with two columns.
2. On pokemon, COUNT(*) returns 949 and COUNT(type_2) returns 510. Why?
-
COUNT(type_2)counts the different values oftype_2 -
COUNT(type_2)only counts the rows wheretype_2is not blank -
COUNT(*)counts some rows twice - 439 rows have an error in
type_2
COUNT(*) counts cards. COUNT(column) counts cards where that line is not NULL. The 439 missing ones are the single-type Pokémon. Different values would be COUNT(DISTINCT type_2).
3. SELECT type_1, COUNT(*) FROM pokemon GROUP BY type_1; returns 18 rows. What does each row stand for?
- One Pokémon
- One pile: all the Pokémon that share the same first type
- One column of the table
- One generation
GROUP BY makes one pile per different value of the column and returns one row per pile. There are 18 different first types, so there are 18 piles and 18 rows.
4. Why does SELECT type_1, pokemon, COUNT(*) FROM pokemon GROUP BY type_1; give an error?
- Because
COUNT(*)needs a column name inside - Because a query cannot select more than two columns
- Because each pile has many names, and the database cannot pick one to print
- Because
pokemonis also the name of the table
The water pile holds 126 cards with 126 names, but the result has one row per pile. In a grouped query every column in SELECT must be a pile key (type_1) or go inside an aggregate.
5. You want the types with more than 50 Pokémon. Where does the condition COUNT(*) > 50 go?
- In
WHERE, beforeGROUP BY - In
HAVING, afterGROUP BY - In
ORDER BY - In
SELECT, next to the column names
The condition is about a whole pile, so it belongs in HAVING. WHERE runs before the piles exist: put it there and PostgreSQL answers aggregate functions are not allowed in WHERE.
6. In which order does the database really execute these lines?
-
SELECT→FROM→WHERE→GROUP BY→HAVING -
FROM→GROUP BY→WHERE→HAVING→SELECT -
FROM→WHERE→GROUP BY→HAVING→SELECT -
FROM→SELECT→WHERE→HAVING→GROUP BY
Open the box (FROM), take cards out (WHERE), make piles (GROUP BY), drop whole piles (HAVING), and only then read the lines and compute (SELECT). You write SELECT first, but it runs fifth.
7. A table sales(shop, product, amount) has one row per sale. You want the total amount per shop, counting only sales above 10 euros, and only for shops with more than 100 of those sales. Pick the two statements that are true.
-
amount > 10goes inWHERE, because it is a question about each sale -
COUNT(*) > 100goes inHAVING, because it is a question about each shop's pile - Both conditions go in
WHERE, joined withAND -
SELECT shop, product, SUM(amount)works withGROUP BY shop
Cards or piles? One sale is a card: WHERE amount > 10. One shop is a pile: HAVING COUNT(*) > 100. The last option fails with must appear in the GROUP BY clause: a shop's pile has many products.
Generation 5 has 156 Pokémon — but which region is generation 5? That is written in another table. Next: SQL Lab: joins, where you read two tables as one, and the 147 cards cause trouble.