Skip to content

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, MIN and MAX
  • Explain why COUNT(*) and COUNT(column) can give different numbers on the same table
  • Split a table into groups with GROUP BY and compute one figure per group
  • Keep only some groups with HAVING
  • Defuse the trap of the day: WHERE and HAVING are not the same thing
  • Read the error must appear in the GROUP BY clause and 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.
  • AS gives 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 BY splits 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

HAVING keeps 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. ROUND tidies it, AS names it with an alias.
  • COUNT(*) counts rows; COUNT(column) counts the rows where that column is not blank. Aggregates skip NULL.
  • GROUP BY makes 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 SELECT is a pile key or sits inside an aggregate.
  • WHERE filters cards before the piles are made; HAVING filters 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 of type_2
  • COUNT(type_2) only counts the rows where type_2 is 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 pokemon is 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, before GROUP BY
  • In HAVING, after GROUP 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 > 10 goes in WHERE, because it is a question about each sale
  • COUNT(*) > 100 goes in HAVING, because it is a question about each shop's pile
  • Both conditions go in WHERE, joined with AND
  • SELECT shop, product, SUM(amount) works with GROUP 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.