Hook: Think of WHERE as the bouncer at the door: only rows that match the rule get into the query result.
Question: What is WHERE in SQL?
Answer: WHERE is the clause you use to filter rows before the final result is returned. It keeps only the records that make a condition true, such as price > 100 or status = 'PAID'. One important detail is that NULL does not behave like a normal value, so you must use IS NULL instead of = NULL.
Interview-Ready Answer: I use WHERE to filter rows in a table or join result before the query finishes. It works with conditions like =, <, IN, BETWEEN, and LIKE, and it is evaluated before grouping and aggregation. A key detail is that NULL needs IS NULL, not = NULL, because SQL treats unknown comparisons differently.
WHERE doesDetailed Explanation: WHERE is the row filter in SQL. Imagine a huge table as a stack of cards; WHERE removes the cards that do not match your rule. The important idea is that it works on rows, not on already-computed summaries.
FROM and any JOINs.WHERE checks each candidate row against the condition.GROUP BY, HAVING, SELECT, ORDER BY, and LIMIT happen.This is why WHERE cannot use a column alias from SELECT in most databases: the alias does not exist yet when filtering happens. It also means WHERE usually cannot filter on an aggregate like COUNT(*); that belongs in HAVING.
NULLSQL has three truth values: true, false, and unknown. NULL means “missing or unknown,” so status = NULL is not true; it is unknown, which behaves like false in WHERE. That is why the correct test is status IS NULL or status IS NOT NULL.
=, <, >, <=, >= for direct comparison.AND, OR, NOT for combining rules.IN for a short list of allowed values.BETWEEN for inclusive ranges.LIKE for simple pattern matching.One very common gotcha is operator precedence. AND binds tighter than OR, so A OR B AND C means A OR (B AND C), not (A OR B) AND C. When in doubt, add parentheses.
WHERE vs HAVING| Clause | Filters | Uses aggregates? | Typical stage |
|---|---|---|---|
| WHERE | Rows | No | Before grouping |
| HAVING | Groups | Yes | After grouping |
Use WHERE when you want to reduce the number of raw rows early. Use HAVING when you want to filter after a GROUP BY, such as “groups with more than 10 orders.”
WHERE mattersWHERE is not just about correctness; it can be a big performance win. A good, index-friendly predicate is often called sargable (search-arg-able), meaning the database can use an index efficiently. A simple equality or range check on an indexed column can often be close to O(log n + k), where k is the number of matching rows. A full table scan is closer to O(n), which hurts as tables grow.
In real systems, a B-tree index often finds rows in just a few page reads even for millions of rows. But patterns like LIKE '%abc' usually cannot use a normal index well because the leading wildcard hides the start of the value. Also, wrapping an indexed column in a function, such as WHERE DATE(created_at) = ..., often makes the predicate less index-friendly unless you have a matching functional index.
Use WHERE whenever you want to narrow a result set early: active users only, orders above a threshold, rows from a date range, or records matching a status list. That early filtering keeps queries easier to read and often faster to run.
Real-World Story: Imagine a checkout service for an e-commerce app. A data analyst needs to see only successful orders over $100 that were created this week, so the dashboard query uses WHERE status = 'PAID' AND amount > 100. This keeps refunded, pending, and test orders out of the report.
What goes wrong when someone misunderstands WHERE? A common bug is writing status = NULL or forgetting parentheses around mixed AND/OR logic. The symptom is either an empty report, or far too many orders showing up. In logs you might see the SQL execute successfully, but the dashboard numbers look suspiciously low or high, and support hears, “Why are canceled orders in the sales chart?”
Another production-style mistake is filtering too late. If you first pull millions of rows and only then try to narrow them in application code, the database does extra work, network traffic grows, and the page may slow down from milliseconds to seconds.
-- Runnable demo: WHERE filters rows, handles NULL correctly, and shows a precedence trap.
-- This script is written in standard, SQLite-friendly SQL.
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer TEXT,
status TEXT,
amount INTEGER NOT NULL,
created_at TEXT NOT NULL
);
INSERT INTO orders (order_id, customer, status, amount, created_at) VALUES
(1, 'Ava', 'PAID', 120, '2024-01-10'),
(2, 'Ben', 'PENDING', 40, '2024-01-11'),
(3, 'Cara', 'PAID', 75, '2024-01-12'),
(4, 'Drew', NULL, 200, '2024-01-12'),
(5, 'Eli', 'REFUNDED', 120, '2024-01-13'),
(6, 'Fay', 'PAID', 25, '2024-02-01');
-- 1) Simple row filtering: keep only paid orders above 100.
SELECT order_id, customer, amount
FROM orders
WHERE status = 'PAID'
AND amount > 100
ORDER BY amount DESC;
-- 2) Failure path: this looks reasonable, but it returns no rows.
-- In SQL, NULL is not equal to anything, not even another NULL.
SELECT order_id, customer, status
FROM orders
WHERE status = NULL;
-- 3) Correct NULL check: use IS NULL.
SELECT order_id, customer, status
FROM orders
WHERE status IS NULL;
-- 4) IN, BETWEEN, LIKE together: a compact, readable filter.
SELECT order_id, customer, status, amount
FROM orders
WHERE status IN ('PAID', 'PENDING')
AND amount BETWEEN 20 AND 120
AND customer LIKE 'A%'
ORDER BY order_id;
-- 5) Precedence trap: AND is evaluated before OR.
-- This means: paid orders, OR pending orders above 100.
SELECT order_id, customer, status, amount
FROM orders
WHERE status = 'PAID'
OR status = 'PENDING' AND amount > 100
ORDER BY order_id;
-- 6) Correct version with parentheses: now BOTH statuses must meet the amount rule.
SELECT order_id, customer, status, amount
FROM orders
WHERE (status = 'PAID' OR status = 'PENDING')
AND amount > 100
ORDER BY order_id;Follow-up & Tricky Questions:
WHERE run compared with GROUP BY? WHERE runs before grouping. It removes raw rows first, and then GROUP BY forms groups from the remaining rows.SELECT alias in WHERE? Because the alias is created later in SQL’s logical order. The filter happens before the projection step that creates the alias.WHERE and HAVING? WHERE filters rows, while HAVING filters grouped results. If your condition uses COUNT, SUM, or another aggregate, you usually need HAVING.NULL affect WHERE? Comparisons with NULL return unknown, so the row is not kept. Use IS NULL or IS NOT NULL instead.WHERE hurt performance? Yes, if the condition prevents index use or if you filter too late. A sargable predicate on an indexed column is much faster than scanning every row.A OR B AND C not behave like I expected? Because AND has higher precedence than OR. SQL reads it as A OR (B AND C), so use parentheses when the meaning matters.WHERE price = NULL return nothing? Because equality does not match NULL. The correct form is WHERE price IS NULL.WHERE? Not directly. Aggregates are produced after WHERE, so use a subquery or HAVING instead.Common Mistakes:
= NULL instead of IS NULL. Correction: NULL needs special handling because it means unknown, not a normal value.AND/OR. Correction: make the intended logic explicit so SQL does not surprise you.WHERE. Correction: filter raw rows in WHERE and grouped results in HAVING.Memory Hook: WHERE is the bouncer at the door: it checks each row before the rest of the query party starts.
Cheat Sheet:
WHERE filters rows.GROUP BY and HAVING.IS NULL, not = NULL.AND binds tighter than OR.HAVING for aggregate filters.Practice Tasks:
PAID.AND/OR using parentheses so the intent is obvious.C and have a non-null status.