Topic 11

Filtering with WHERE

SQL

"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:

A WHERE clause — one true-or-false test, run against every row
SELECT title, rating
FROM films
WHERE rating = 'PG';
titlerating
The Long RainPG
Paper LanternsPG

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.

Rows streaming through the WHERE gate
All rows arriveThe Long Rain · Paper Lanterns · Night Bus
WHERE rating = 'PG'one test per row
True — keptThe Long Rain · Paper Lanterns
False — droppedNight Bus (PG-13)

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:

Two conditions — a row must pass both
SELECT title, runtime_min, rating
FROM films
WHERE rating = 'PG' AND runtime_min < 100;
titleruntime_minrating
Paper Lanterns96PG

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:

The trap — this does not mean what it says in English
SELECT title, runtime_min, rating
FROM films
WHERE rating = 'PG' OR rating = 'PG-13' AND runtime_min < 100;
titleruntime_minrating
The Long Rain112PG
Paper Lanterns96PG
Night Bus98PG-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:

The fix — parentheses group the OR before the AND applies
SELECT title, runtime_min, rating
FROM films
WHERE (rating = 'PG' OR rating = 'PG-13') AND runtime_min < 100;
titleruntime_minrating
Paper Lanterns96PG
Night Bus98PG-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:

IS NULL — the honest way to ask about missing values
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.

Common Confusions
  • "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.
  • "= NULL should work." NULL means unknown, and unknown never equals anything — the test just never comes back true. IS NULL is the honest question, built for exactly this.
  • "OR means what English means." rating = 'PG' OR rating = 'PG-13' AND runtime_min < 100 doesn'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.
Why It Matters
  • 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