WHERE: telling true rows from false ones
Comparison, AND and OR, LIKE patterns — and the strange rule that NULL is never equal to anything, not even to itself.
Last lesson you filtered rows with WHERE. Now we look properly at what a condition can say, because filtering is where most wrong answers are born. The canteen keeps a second table, orders, with one row per order: what was bought, for how much, and whether it is paid. One row has paid = NULL: the canteen does not know yet.
orders
+----+--------+-------+----------+
| id | item | price | paid |
+----+--------+-------+----------+
| 1 | soup | 2.50 | yes |
| 2 | salad | 2.80 | no |
| 3 | juice | 1.90 | yes |
| 4 | juice | 1.90 | NULL |
| 5 | soup | 2.50 | yes |
+----+--------+-------+----------+The orders table: one row per order, and one unknown value in paid.A comparison like price < 2.00 or paid = 'yes' does not describe the table — it is a question you ask each row separately. Every row answers yes, no, or I cannot say. WHERE keeps exactly the yes rows. That is why the same query can return a different number of rows tomorrow: the question is stable, the data is not.
Read both queries over the orders table and predict the rows before looking at the answer. AND narrows: one condition is about payment, the other about price, and both must hold. OR widens: a row survives if at least one side is true, so price alone saves the cheap rows.
WHERE paid = 'yes' AND price < 2.00
rows kept: juice (1.90, yes)
WHERE paid = 'yes' OR price < 2.00
rows kept: soup, juice, juice, soupTwo conditions over the same table: rows kept by AND, then by OR.AND binds tighter than OR, exactly like multiplication before addition in arithmetic. If you mean something else, put parentheses around it. WHERE a = 1 OR b = 2 AND c = 3 means a = 1 OR (b = 2 AND c = 3), and people get this wrong constantly — the parentheses cost nothing.
Equality demands the whole value to match. LIKE matches a pattern instead: % stands for any text of any length, including nothing. So dish LIKE 'veggie%' catches everything that starts with veggie, and '%soup%' catches everything with soup anywhere inside.
menu
+----+---------------+
| id | dish |
+----+---------------+
| 1 | veggie soup |
| 2 | chicken soup |
| 3 | veggie salad |
+----+---------------+
WHERE dish LIKE 'veggie%' matches: veggie soup, veggie salad
WHERE dish LIKE '%soup%' matches: veggie soup, chicken soupThe menu table and two LIKE patterns over the dish column.Now the strangest rule in SQL. The row with paid = NULL disappears from every comparison query. Not from paid = 'no' and not from paid = 'yes' — from both. To understand why, look at what NULL actually means.
WHERE paid = 'yes'
rows kept: soup (1), juice (3), soup (5)
row 4: paid is NULL, dropped
NULL = NULL is not true, it is unknownWhy paid = 'yes' drops the NULL row, and why NULL = NULL is not true.NULL is not a value, it is the absence of one: the canteen does not know yet whether that order was paid. Asking is an unknown thing equal to yes is like asking is my tomorrow headache equal to yes — the honest answer is not no, it is unknown. SQL calls that unknown, and WHERE keeps only true, so the row is dropped.
Practice
The canteen wants a list of items that were paid and sold three or more times in one day. Write a query over the sales table: it has columns item, count and paid, where paid is the text 'yes' or 'no'.
Hints
- Two conditions must hold at the same time, and in SQL that is one word.
- Compare text with single quotes and numbers without: paid = 'yes' AND count >= 3.
- The column that measures how often an item was sold is called count.
Five rows of orders are checked with WHERE paid = 'yes'. One row has paid = NULL. How many rows come back, and what happens to the NULL row?
Hints
- For each of the five rows ask: is the condition true, false or unknown?
- A row is kept only when the WHERE condition is exactly true; unknown is thrown out like false.
Match each pattern with what it catches in the dish column.
Hints
- The % sign stands for any text, any length: before, after or both.
- Think about where the pattern is anchored: 'A%' must start with A, '%A' must end with A.