Skip to content

Exercise 2 of 10 · Filtering with WHERE

Pick the Right Rows

What you will make

A shortlist of the cheap garden tools in a lending library, produced by tightening one condition in a WHERE clause that currently returns most of the table.

The one new idea: AND narrows a filter while OR widens it

A filter that returns too much looks exactly like a filter that works, and nothing in the output warns you. Reading a WHERE clause back as an English sentence — and knowing whether AND or OR matches that sentence — is the difference between a report you can sign off and one that is quietly wrong.

Go straight to the code ↓

WHERE tests one row at a time

A WHERE clause is a question asked of each row on its own. Rows that answer true come back; rows that answer false do not. Nothing else about the query changes — the columns you asked for are still the columns you get.

When a filter has two parts, the word joining them decides everything:

  • AND — keep the row only if both parts are true. Adding an AND can only ever make the result smaller.
  • OR — keep the row if at least one part is true. Adding an OR can only ever make it bigger.

That sounds obvious written down, and it still catches people constantly, because English is sloppier than SQL. "Show me the garden tools and the cheap ones" means OR. "Show me the cheap garden tools" means AND. The same two conditions, joined two different ways, by a sentence that barely changed.

SQL
CREATE TABLE birds (id INTEGER PRIMARY KEY, species TEXT, habitat TEXT, seen INTEGER);
INSERT INTO birds (species, habitat, seen) VALUES
  ('wren', 'hedge', 12),
  ('heron', 'water', 1),
  ('moorhen', 'water', 9),
  ('dunnock', 'hedge', 3);

SELECT species FROM birds WHERE habitat = 'water' OR seen < 5;

SELECT species FROM birds WHERE habitat = 'water' AND seen < 5;
Output
species
-------
heron
moorhen
dunnock
(3 rows)

species
-------
heron
(1 row)

Three rows, then one. The OR version collected everything that was either a water bird or rarely seen; the AND version kept only the row that was both.

The third answer: unknown

SQL comparisons do not return only true and false. Compare anything with NULL — which means "no value recorded" — and the answer is unknown, and WHERE keeps only rows that came out true.

SQL
CREATE TABLE birds (id INTEGER PRIMARY KEY, species TEXT, seen INTEGER);
INSERT INTO birds (species, seen) VALUES ('wren', 12), ('nightjar', NULL), ('dunnock', 3);

SELECT species FROM birds WHERE seen < 5;

SELECT species FROM birds WHERE seen >= 5;

SELECT species FROM birds WHERE seen < 5 OR seen IS NULL;
Output
species
-------
dunnock
(1 row)

species
-------
wren
(1 row)

species
--------
nightjar
dunnock
(2 rows)

Two filters that look like opposites returned one row each, out of three rows. The nightjar has no count, so it is not known to be under five and not known to be five or more either; it falls out of both. Only the third query, which says so explicitly with IS NULL, brings it back.

The table in the editor has a row like that, and the answer you are working towards correctly leaves it out. Knowing why it is out — rather than not noticing — is the point.

Your turn

The editor holds a seven-row tools table and a query that is meant to answer one question: which garden tools have a deposit under 1000?

Press Run first. Five rows come back. Among them are the hand saw, which is not on the garden shelf at all, and the post driver at 1100, which is well over the limit — neither belongs in the answer.

Change one word in the WHERE line so the query returns only the rows where both conditions hold, and run it again. You should get two rows, cheapest first.

Then try putting the OR back and reading the five-row result again, to see exactly which rows each version lets through.

If something goes wrong

If you get seven rows, the WHERE line has probably been deleted. Without it a SELECT returns every row.

If you get a syntax error pointing at a < or a >, the comparison operators have been written the wrong way round: they are <= and >=, with the angle bracket first.

If you get three rows rather than two, the donated ladder may have been brought back with an OR deposit IS NULL. That is a perfectly good clause, and it is not what this question asks for.

Nothing here can break. The database is built fresh and thrown away on every run.

Write your code

Runs in your browser. Press Run (or Ctrl/Cmd+Enter) and the output is checked for you.

Ctrl/Cmd+Enter to run

Press Esc then Tab to move keyboard focus out of the code editor.

Ready
Output will appear here after you run your code.

The runtime is starting in the background. You can type now — it will be ready before you are.

The answer appears here once you have run your code at least once.

Things that often go wrong here

Assuming the donated ladder should be in the result
Its shelf is garden, so half the condition passes, but its deposit is NULL — unknown. An unknown number is not known to be under 1000, so the comparison is neither true nor false and WHERE keeps only rows that came out true. The row is dropped with no mention of it. If you wanted it, you would have to say `AND (deposit < 1000 OR deposit IS NULL)`.
Adding brackets instead of changing the operator
`WHERE (shelf = 'garden') OR (deposit < 1000)` is exactly the same query with more punctuation. Brackets change how AND and OR combine when both are present; with a single OR there is nothing for them to regroup.
Writing `deposit =< 1000`
SQL spells the operator `<=`, with the angle bracket first, and `=<` is a syntax error that stops the run. The same goes for `=>`, which should be `>=`. Only `<>` puts the angle brackets in that order, and it means "not equal".
Using `==` for equality
SQLite happens to accept `==` as well as `=`, so `shelf == 'garden'` works here and gives you no warning. PostgreSQL and MySQL reject it. Equality in SQL is a single `=`; save yourself the habit now.

Longer explanation: read the full lesson. Want a blank editor instead? Open the SQL playground.