Filtering with WHERE
"Show me all the films" is rarely the question Lora actually has. Her real questions come with conditions attached: the PG films, the films under 100 minutes, the customers who joined this month. WHERE is the clause — the part of a query — that narrows the answer from everything down to exactly the rows that matter.
WHERE also gives you your first taste of thinking the way the database thinks. It doesn't scan the table looking for a vibe; it takes one true-or-false test and runs it against every row, one row at a time. True, the row stays in the answer. False, the row is dropped. Adopt that row-by-row picture now and everything later in the book becomes legible.
One Test per Row
Here is the smallest possible filter. In English: show the title and rating of each film, but only where the rating equals PG. In SQL:
SELECT title, rating FROM films WHERE rating = 'PG';
| title | rating |
|---|---|
| The Long Rain | PG |
| Paper Lanterns | PG |
The database walked all three film rows, asked each one "is your rating PG?", kept the two that said true, and dropped Night Bus, whose PG-13 said false. Think of a door check at a venue: each guest — each row — is checked against the same rule, and the rule never changes mid-line. Map the picture, keep the mechanics: one condition, tested per row, keep or discard. That's WHERE, and we'll use the real terms from here.
The Comparison Toolkit
Equality is one test among several. The comparison operators are mostly the ones you know from school math: = for equals, <> for not-equals, and <, >, <=, >= for the orderings. Films shorter than 100 minutes is WHERE runtime_min < 100; everything except PG films is WHERE rating <> 'PG'. The only surprise in the set is that not-equals is written with angle brackets rather than an exclamation mark, though many engines accept != too.
Notice the quoting: 'PG' wears quotes, 100 doesn't. Text values always go in single quotes, so the database can tell the word from a column name; numbers stand bare, exactly as Chapter 2's types promised they could. One honest engine note while we're here: whether 'PG' also matches a lowercase 'pg' depends on the engine — some compare text case-sensitively, some don't — so professionals never rely on it either way. You'll meet the standard trick for taming case in Topic 13.
Combining Tests: AND, OR, NOT
Real questions stack conditions, and SQL stacks them with the words you'd use in English. PG films under 100 minutes becomes two tests joined by AND — a row must pass both to stay:
SELECT title, runtime_min, rating FROM films WHERE rating = 'PG' AND runtime_min < 100;
| title | runtime_min | rating |
|---|---|---|
| Paper Lanterns | 96 | PG |
The Long Rain passed the rating test but failed the runtime test at 112 minutes, so only Paper Lanterns survives. OR works the same way but keeps a row if either test passes, and NOT flips a test. So far, so English. The trap arrives when AND and OR meet in one clause.
Say Lora wants PG or PG-13 films that run under 100 minutes. Written the way you'd say it, the query looks right and answers wrong:
SELECT title, runtime_min, rating FROM films WHERE rating = 'PG' OR rating = 'PG-13' AND runtime_min < 100;
| title | runtime_min | rating |
|---|---|---|
| The Long Rain | 112 | PG |
| Paper Lanterns | 96 | PG |
| Night Bus | 98 | PG-13 |
All three films came back — including a 112-minute one in what was supposed to be an under-100 list. The reason: AND binds tighter than OR, the way multiplication binds tighter than addition. The database read the clause as "rating is PG, or (rating is PG-13 and runtime under 100)" — and every PG film passes that first half no matter how long it runs. Parentheses make the sentence you actually meant:
SELECT title, runtime_min, rating FROM films WHERE (rating = 'PG' OR rating = 'PG-13') AND runtime_min < 100;
| title | runtime_min | rating |
|---|---|---|
| Paper Lanterns | 96 | PG |
| Night Bus | 98 | PG-13 |
Now the rating test is settled first, and the runtime test applies to everyone. The professional habit is simple: the moment one WHERE clause contains both AND and OR, add parentheses — even when they turn out to be unnecessary, they make the query say what you meant out loud.
When NULL Strikes
Chapter 2 made a promise about NULL — the marker for "no value here" — and this is where it comes due. A few of the Marquee's customers have no email on file. The obvious query for finding them looks like this, and it returns nothing at all: WHERE email = NULL. Zero rows. Not an error, just silence.
The reason is NULL's whole personality: NULL means unknown, and an unknown value never equals anything — not even another unknown. The test "does this unknown equal NULL?" can't come back true, so no row ever passes it. SQL provides special forms for exactly this question, and they read like honest English:
SELECT name FROM customers WHERE email IS NULL;
| name |
|---|
| Ben |
| Rosa |
| Tomas |
Three of the Marquee's 212 customers have no email on file, and IS NULL finds them where = NULL found nothing. The mirror form IS NOT NULL keeps only rows where a value is present. File both away as a pair of special verbs reserved for missing data — every SQL beginner runs the = NULL query exactly once, gets the eerie empty answer, and never forgets it.
- "WHERE picks columns." WHERE picks rows; SELECT picks columns. Those are the two axes of every query — which facts to keep, and which parts of each fact to show.
- "
= NULLshould work." NULL means unknown, and unknown never equals anything — the test just never comes back true.IS NULLis the honest question, built for exactly this. - "OR means what English means."
rating = 'PG' OR rating = 'PG-13' AND runtime_min < 100doesn't group the way you'd say it — AND binds first. Parentheses make the sentence you meant. - "A dropped row is gone from the table." WHERE drops rows from the answer only. Night Bus is still in the films table; it just didn't appear in that result.
- Real tables hold millions of rows. WHERE is the difference between an answer and a data dump — nobody reads a million rows, but everyone reads the twelve that match.
- The row-by-row true/false model is exactly how the database thinks. Adopt it now and JOINs, GROUP BY, and every debugging session ahead become legible.
Knowledge Check
In one sentence: what does WHERE choose, and what does SELECT choose?
- WHERE chooses columns; SELECT chooses rows
- WHERE chooses rows; SELECT chooses columns
- WHERE chooses the table; SELECT chooses rows
- Both choose rows; SELECT just runs second
Lora wants PG or PG-13 films under 100 minutes and writes WHERE rating = 'PG' OR rating = 'PG-13' AND runtime_min < 100. Why does the 112-minute The Long Rain appear?
- Because runtime_min is stored as plain text, so the numeric comparison silently fails
- Because AND groups first, so any PG film passes without a runtime check
- Because SQL ignores everything after the first OR
- Because OR doesn't work in a WHERE clause
The query SELECT name FROM customers WHERE email = NULL; returns zero rows even though three customers have no email. Why?
- The query has a hidden syntax error in it, so it silently fails and returns nothing
- Rows with missing values are invisible to WHERE
- NULL needs quotes:
email = 'NULL'would find them - Unknown never equals anything, so the test is never true; use IS NULL
After WHERE rating = 'PG' drops Night Bus from the answer, what happened to Night Bus in the films table?
- Nothing — it's still there; it just wasn't in that result
- It was deleted and would need to be re-added
- It was moved to a hidden archive table
- It's now quietly flagged as excluded for all your future queries too
You got correct