WHERE filters individual rows after FROM but before GROUP BY, SELECT, and ORDER BY
AND requires both conditions true; OR requires either true — use parentheses to control precedence
BETWEEN is inclusive on both ends: BETWEEN 10 AND 20 includes 10 and 20
IN is shorthand for multiple OR conditions; LIKE uses % for any sequence and _ for a single character
NULL comparison requires IS NULL / IS NOT NULL — WHERE col = NULL always returns zero rows
Biggest mistake: WHERE country = 'USA' OR country = 'UK' AND revenue > 1000 — AND binds tighter than OR, producing wrong logic without parentheses
Plain-English First
Imagine you have a massive filing cabinet with thousands of customer folders. The WHERE clause is like telling your assistant: 'Only bring me folders where the customer lives in New York AND spent more than $500.' Instead of dumping every single folder on your desk, you get exactly the ones you need. That's all WHERE does — it filters rows from a table so you only see the data that matches your conditions.
Every real-world database holds thousands, sometimes millions of rows. A table of online orders might have five million records going back a decade. Without a way to pinpoint just the rows you care about, querying a database would be like searching for a specific email by reading your entire inbox from the beginning. The WHERE clause is the most fundamental filtering tool in SQL — and it's why databases are actually useful in practice, not just in theory.
Before WHERE existed as a concept, you'd have to pull every row into your application and filter it in code. That means your server drags across the network, your app chews through memory, and your users wait. WHERE pushes the filtering down to the database engine itself — the place best equipped to do it fast, using indexes. It solves the problem of unnecessary data transfer and processing at its source.
By the end of this article you'll know how to write WHERE clauses from scratch, combine multiple conditions using AND, OR, and NOT, use powerful operators like BETWEEN, LIKE, and IN, and avoid the three beginner mistakes that silently break your queries. You'll also walk away with the answers to the interview questions that trip up even developers who've been writing SQL for a year.
What the WHERE Clause Actually Does (And Why It Belongs After FROM)
Every SQL SELECT statement follows a logical order: you tell the database WHAT columns you want (SELECT), then WHERE to look for rows (FROM), and then WHICH rows to keep (WHERE). The WHERE clause acts as a gatekeeper — the database evaluates every row in the table against your condition, and only the rows that pass get returned.
Think of it like a bouncer at a club checking IDs. Every single person in the queue gets checked. If your condition says 'age >= 21', only people who meet that rule get in. Everyone else is turned away quietly — they don't throw an error, they just don't appear in your results.
The clause uses standard comparison operators you already know from maths: = (equals), != or <> (not equals), > (greater than), < (less than), >= (greater than or equal to), and <= (less than or equal to). You attach the condition to a column name, and the database tests each row against it.
One thing that trips beginners up: WHERE is evaluated BEFORE SELECT. The database finds matching rows first, then decides which columns to show. This matters when you start writing more advanced queries.
basic_where_filter.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- Imagine a small e-commerce database.-- This is our 'orders' table with real-looking data.-- First, let's see ALL orders (no filter) so we understand what we're working withSELECT order_id, customer_name, country, total_amount
FROM orders;
-- Output above shows 6 rows. Now let's FILTER:-- We only want orders from customers in the USA.-- The WHERE clause checks 'country' for every row.SELECT order_id, customer_name, country, total_amount
FROM orders
WHERE country = 'USA'; -- Only rows where country equals 'USA' pass through-- Now filter by a number — orders over $300SELECT order_id, customer_name, country, total_amount
FROM orders
WHERE total_amount > 300; -- '>' means strictly greater than, so 300.00 itself is excluded-- Not equal to — find everyone NOT from CanadaSELECT order_id, customer_name, country, total_amount
FROM orders
WHERE country != 'Canada'; -- <> works identically: WHERE country <> 'Canada'
Watch Out: String Values Need Quotes, Numbers Don't
Write WHERE country = 'USA' with single quotes around the text. Write WHERE total_amount > 300 with NO quotes around the number. If you write WHERE total_amount > '300', most databases silently cast it and it works — but you're relying on implicit type conversion, which can produce wrong results with certain data types. Be explicit from day one.
Production Insight
WHERE runs before GROUP BY and before SELECT aliases exist — it cannot reference computed column names.
The database evaluates WHERE first to eliminate rows before doing any expensive aggregation or sorting.
A WHERE clause that eliminates 99% of rows makes every subsequent operation faster — always filter early.
Key Takeaway
WHERE is the primary performance lever — it runs before aggregation, projection, and sorting.
SQL execution order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY.
Index your most selective WHERE columns — that's where the B-tree lookup happens.
Combining Conditions with AND, OR, and NOT — The Logic Operators
One condition is useful. Multiple conditions are powerful. SQL gives you three logic operators to combine conditions: AND, OR, and NOT.
AND means BOTH conditions must be true. Think of it as a strict filter — like finding customers who live in the USA AND spent more than $300. Both boxes must be ticked.
OR means AT LEAST ONE condition must be true. Think of a wider net — customers from the USA OR customers from Canada. Either one qualifies.
NOT flips a condition on its head. WHERE NOT country = 'USA' is the same as WHERE country != 'USA'. It's most useful with operators like IN and LIKE, which we'll cover shortly.
Here's the critical thing beginners miss: AND has higher precedence than OR, just like multiplication beats addition in maths. So WHERE country = 'USA' OR country = 'Canada' AND total_amount > 300 does NOT mean what you think. The AND runs first, binding only the Canada condition to the amount check. Always use parentheses to make your logic explicit and readable.
and_or_not_conditions.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- AND: both conditions must be true-- Find USA customers who spent more than $300SELECT order_id, customer_name, country, total_amount
FROM orders
WHERE country = 'USA'AND total_amount > 300; -- Both must match — the row clears TWO checkpoints-- OR: at least one condition must be true-- Find anyone from the USA OR from IndiaSELECT order_id, customer_name, country, total_amount
FROM orders
WHERE country = 'USA'OR country = 'India'; -- Passes if EITHER condition is true-- NOT: inverts the condition-- Find every order that is NOT from the USASELECT order_id, customer_name, country, total_amount
FROM orders
WHERENOT country = 'USA'; -- Equivalent to: WHERE country != 'USA'-- MIXING AND + OR — always use parentheses to be safe!-- Find: (USA customers over $300) OR (any Indian customer)SELECT order_id, customer_name, country, total_amount
FROM orders
WHERE (country = 'USA' AND total_amount > 300) -- Group 1 with parenthesesOR (country = 'India'); -- Group 2 with parentheses
Any time you mix AND with OR, wrap each logical group in parentheses. It costs you nothing in performance, and it makes your intent crystal clear to every developer (including future you) who reads the query six months from now. WHERE (a AND b) OR (c) is always safer than WHERE a AND b OR c.
Production Insight
AND binds before OR in SQL just like multiplication before addition in arithmetic — mixing them without parentheses is the most common logic bug in WHERE clauses.
Always use parentheses when combining AND and OR, even when you think the precedence is correct.
Test mixed conditions with a COUNT(*) first to confirm the expected row count before running the full query.
Key Takeaway
AND binds before OR — always use parentheses when combining them.
Missing parentheses in OR/AND expressions produce wrong rows, not errors — the bug is silent.
Test every complex WHERE with a COUNT(*) against your expected number before shipping.
BETWEEN, IN, and LIKE — The Power Operators That Replace Messy Conditions
Once you've got AND, OR, and NOT down, SQL gives you three shortcut operators that make common filtering patterns much cleaner to write and read.
BETWEEN filters rows within a range — it's shorthand for >= and <= combined. WHERE total_amount BETWEEN 100 AND 500 is identical to WHERE total_amount >= 100 AND total_amount <= 500. Both ends are inclusive, meaning 100 and 500 themselves are included.
IN filters against a list of specific values — it's shorthand for chaining multiple OR equals conditions. WHERE country IN ('USA', 'Canada', 'India') beats writing three separate OR conditions, especially when that list grows to ten items.
LIKE is for pattern matching on text. The % symbol means 'any sequence of characters', and the _ symbol means 'exactly one character'. WHERE customer_name LIKE 'A%' finds every name starting with A. WHERE customer_name LIKE '_a%' finds names where the second character is 'a'.
NOT BETWEEN, NOT IN, and NOT LIKE all work as inverses. They're your go-to tools for exclusion filtering.
between_in_like_operators.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- BETWEEN: find orders where total_amount is between $100 and $500 (inclusive)SELECT order_id, customer_name, total_amount
FROM orders
WHERE total_amount BETWEEN100AND500; -- Includes rows where amount = 100 or 500 exactly-- NOT BETWEEN: orders outside that range (under $100 or over $500)SELECT order_id, customer_name, total_amount
FROM orders
WHERE total_amount NOTBETWEEN100AND500;
-- IN: cleaner alternative to multiple OR conditions-- Find customers from any of three specific countriesSELECT order_id, customer_name, country
FROM orders
WHERE country IN ('USA', 'Canada', 'India'); -- Much cleaner than 3 separate OR clauses-- NOT IN: exclude those same countriesSELECT order_id, customer_name, country
FROM orders
WHERE country NOTIN ('USA', 'Canada', 'India');
-- LIKE: pattern matching on text-- Find all customers whose name STARTS WITH the letter 'A'SELECT order_id, customer_name
FROM orders
WHERE customer_name LIKE 'A%'; -- % matches zero or more of ANY character-- Find customers whose name CONTAINS 'son' anywhereSELECT order_id, customer_name
FROM orders
WHERE customer_name LIKE '%son%'; -- % on both sides = 'son' can appear anywhere-- _ matches exactly ONE character-- Find 5-letter names (4 wildcards + 1 specific? No — 5 underscores = exactly 5 chars)SELECT order_id, customer_name
FROM orders
WHERE customer_name LIKE '_____'; -- Exactly 5 characters in the name
Output
-- BETWEEN $100 AND $500:
order_id | customer_name | total_amount
----------+----------------+--------------
1001 | Alice Johnson | 450.00
1003 | Priya Sharma | 310.00
1004 | Marcus Webb | 120.00
(3 rows)
-- IN ('USA', 'Canada', 'India'):
order_id | customer_name | country
----------+----------------+---------
1001 | Alice Johnson | USA
1003 | Priya Sharma | India
1004 | Marcus Webb | USA
1006 | Sandra Lee | USA
(4 rows)
-- LIKE 'A%' (names starting with A):
order_id | customer_name
----------+---------------
1001 | Alice Johnson
(1 row)
-- LIKE '%son%' (names containing 'son'):
order_id | customer_name
----------+---------------
1001 | Alice Johnson
(1 row)
Interview Gold: BETWEEN Is Always Inclusive
Interviewers love asking whether BETWEEN includes the boundary values. It always does — BETWEEN 100 AND 500 is mathematically >= 100 AND <= 500. If you want to exclude the boundaries, drop back to plain comparison operators: WHERE amount > 100 AND amount < 500.
Production Insight
LIKE '%pattern%' with a leading wildcard forces a full table scan — the index on the column cannot be used.
For full-text search requirements, use a proper full-text index (see Full-Text Search in SQL) rather than LIKE '%term%'.
LIKE 'prefix%' can use an index; LIKE '%suffix' and LIKE '%middle%' cannot.
Key Takeaway
LIKE 'prefix%' uses the index; LIKE '%anything' does not — leading wildcards kill index usage.
For contains-search at scale, full-text indexes (PostgreSQL tsvector, MySQL FULLTEXT) replace LIKE '%term%'.
IN is shorthand for OR on the same column — more readable and equally performant.
Filtering NULL Values — The Special Case That Breaks Beginners
NULL in SQL doesn't mean zero. It doesn't mean an empty string. It means unknown or absent — there is no value there at all. This distinction matters enormously when filtering.
Here's the trap: you cannot use = to check for NULL. Writing WHERE phone_number = NULL will never return any rows — not because there are none, but because NULL = NULL evaluates to NULL (unknown), not TRUE. The database sees 'I don't know if this equals nothing' and skips the row.
The correct operators are IS NULL and IS NOT NULL. These are the only two ways to reliably filter on the presence or absence of a value.
This also affects AND and OR. If any part of a compound condition involves NULL, the result can be NULL rather than TRUE or FALSE, causing rows to silently vanish from your results. When you're debugging a query that returns fewer rows than expected, checking for unexpected NULLs in your filter columns is always a smart first step.
Real-world scenario: a customers table might have a phone_number column where some customers haven't provided a number. IS NULL helps you find them so you can prompt them to update their profile.
filtering_null_values.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- Let's say our 'customers' table has an optional 'phone_number' column.-- Some customers filled it in, some didn't.-- WRONG WAY: this returns 0 rows, always, even if NULLs existSELECT customer_id, customer_name, phone_number
FROM customers
WHERE phone_number = NULL; -- NULL = NULL is NULL (unknown), not TRUE — NEVER do this-- RIGHT WAY: use IS NULL to find customers with no phone numberSELECT customer_id, customer_name, phone_number
FROM customers
WHERE phone_number ISNULL; -- 'IS NULL' is the only reliable NULL check-- IS NOT NULL: find customers who DID provide a phone numberSELECT customer_id, customer_name, phone_number
FROM customers
WHERE phone_number ISNOTNULL; -- Only returns rows where a value actually exists-- Combining NULL checks with other conditions-- Find customers from the USA who haven't provided a phone numberSELECT customer_id, customer_name, country, phone_number
FROM customers
WHERE country = 'USA'AND phone_number ISNULL; -- Both conditions must be true
Output
-- IS NULL result (customers with no phone number):
customer_id | customer_name | phone_number
-------------+----------------+--------------
2002 | Marcus Webb | NULL
2005 | Sandra Lee | NULL
(2 rows)
-- IS NOT NULL result (customers with a phone number on file):
customer_id | customer_name | phone_number
-------------+----------------+--------------
2001 | Alice Johnson | 555-0142
2003 | Priya Sharma | 555-0198
2004 | Tom Nguyen | 555-0237
(3 rows)
Watch Out: NULL Is Not Zero and Not Empty String
WHERE phone_number = '' finds rows with an empty string — a value that exists but contains nothing. WHERE phone_number IS NULL finds rows where no value was ever stored. These are different things. A column can have an empty string AND not be NULL. Always check which you actually have in your data before filtering.
Production Insight
WHERE col = NULL is the most common silent SQL bug — it returns zero rows without an error.
NULL comparisons propagate: NULL = NULL evaluates to UNKNOWN, not TRUE.
COALESCE in WHERE (WHERE COALESCE(col, 'default') = 'default') works but prevents index usage — use IS NULL instead.
Key Takeaway
NULL = NULL is UNKNOWN in SQL, not TRUE — use IS NULL and IS NOT NULL exclusively for NULL checks.
Any arithmetic or comparison involving NULL produces NULL — the result disappears from WHERE results.
IS NOT NULL should be added explicitly to any column that might be NULL when it shouldn't affect results.
● Production incidentPOST-MORTEMseverity: high
Operator Precedence Bug Emailed the Wrong 40,000 Customers
Symptom
Campaign response rates and cost-per-acquisition were drastically off. Investigation revealed the email went to all UK customers regardless of tier, and only premium US customers.
Assumption
The developer wrote WHERE country = 'UK' OR country = 'US' AND tier = 'premium' expecting it to filter premium customers from both countries.
Root cause
AND has higher precedence than OR. The query was evaluated as WHERE country = 'UK' OR (country = 'US' AND tier = 'premium') — all UK customers passed regardless of tier.
Fix
Added parentheses: WHERE (country = 'UK' OR country = 'US') AND tier = 'premium'. Added a mandatory WHERE clause review step to the marketing query approval process.
Key lesson
AND has higher precedence than OR in SQL — always use parentheses when mixing the two
Test WHERE clause logic with COUNT(*) before running any data modification or bulk send
Peer-review all marketing queries against a user count expectation before execution
Production debug guideDiagnosing zero rows, unexpected inclusions, and NULL filter failures3 entries
Symptom · 01
WHERE filter returns zero rows despite data existing
→
Fix
Check for NULL: WHERE phone = NULL always returns zero rows. Use WHERE phone IS NULL. Also check data type mismatch: WHERE id = '123' (string) on an integer column may work in MySQL but fails in PostgreSQL.
Symptom · 02
OR condition includes rows that should be excluded
→
Fix
Operator precedence: AND binds before OR. Add parentheses: WHERE (country = 'UK' OR country = 'US') AND tier = 'premium'. Without parentheses, WHERE country = 'UK' OR country = 'US' AND tier = 'premium' only applies the tier filter to US rows.
Symptom · 03
LIKE pattern not matching expected rows
→
Fix
Check for leading/trailing spaces in the data: TRIM(column) LIKE '%pattern%'. Also verify case sensitivity — LIKE is case-insensitive in MySQL but case-sensitive in PostgreSQL. Use ILIKE for case-insensitive in PostgreSQL.
Operator
What It Does
Example
Returns Rows When...
=
Exact match
WHERE country = 'USA'
Column value exactly equals the given value
!= or <>
Not equal
WHERE country != 'USA'
Column value is anything other than the given value
> / <
Greater / Less than
WHERE amount > 300
Column value is strictly above or below the threshold
>= / <=
Greater or equal / Less or equal
WHERE amount >= 300
Column value meets or exceeds / meets or falls below threshold
BETWEEN x AND y
Range (inclusive both ends)
WHERE amount BETWEEN 100 AND 500
Value is >= x AND <= y
IN (list)
Matches any value in a list
WHERE country IN ('USA','India')
Column value appears anywhere in the provided list
LIKE 'pattern'
Partial text match
WHERE name LIKE 'A%'
Text matches the pattern (% = any chars, _ = one char)
IS NULL
No value stored
WHERE phone IS NULL
Column contains NULL (no value)
IS NOT NULL
A value exists
WHERE phone IS NOT NULL
Column contains any non-NULL value
NOT
Inverts any condition
WHERE NOT country = 'USA'
The inner condition evaluates to FALSE
Key takeaways
1
WHERE is evaluated before SELECT
the database finds matching rows first, then picks which columns to display. This is why you can't filter on a column alias defined in your SELECT list.
2
NULL means unknown, not zero or empty. Only IS NULL and IS NOT NULL reliably detect it. Using = NULL always returns nothing, silently, with no error.
3
BETWEEN is always inclusive on both ends
BETWEEN 100 AND 500 includes 100 and 500 themselves. Use plain > and < operators if you need exclusive boundaries.
4
Always use parentheses when mixing AND with OR. AND has higher precedence than OR just like * beats + in maths, so unparenthesised mixed logic produces results that look right but filter incorrectly.
Common mistakes to avoid
3 patterns
×
Using = NULL to compare NULL values
Symptom
WHERE phone_number = NULL returns zero rows silently — even if the column has many NULL rows. No error is thrown.
Fix
Use IS NULL for equality: WHERE phone_number IS NULL. Use IS NOT NULL to exclude nulls. The = operator produces UNKNOWN (not TRUE) when either operand is NULL, and UNKNOWN rows are excluded from WHERE results.
×
Mixing AND and OR without parentheses
Symptom
Wrong rows included or excluded — the query runs without error but the logic is different from intent. WHERE country = 'USA' OR country = 'UK' AND revenue > 1000 filters revenue only for UK rows.
Fix
Always use parentheses when mixing AND and OR: WHERE (country = 'USA' OR country = 'UK') AND revenue > 1000. AND has higher precedence than OR — parentheses make intent explicit and prevent precedence bugs.
×
Wrapping numbers in quotes in WHERE conditions
Symptom
Wrong results or implicit type coercion — WHERE total_amount > '300' performs string comparison ('9' > '300' is true), not numeric comparison
Fix
Match the data type in WHERE to the column's data type. Numbers: WHERE total_amount > 300. Strings: WHERE status = 'active'. Dates: WHERE created_at >= '2026-01-01' (the date string is cast automatically in most databases).
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the difference between WHERE and HAVING in SQL?
Q02JUNIOR
Why does WHERE column = NULL return no rows?
Q03JUNIOR
If you write WHERE status = 'active' OR status = 'pending' AND total > 5...
Q01 of 03JUNIOR
What is the difference between WHERE and HAVING in SQL?
ANSWER
WHERE filters individual rows before GROUP BY runs — it operates on raw row data and cannot reference aggregate functions. HAVING filters groups after GROUP BY and aggregation — it can use aggregate conditions like HAVING COUNT(*) > 5. SQL logical execution order is FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. Both can appear in the same query: WHERE filters rows before grouping (reducing the rows that get aggregated), and HAVING filters groups after aggregation (removing groups that don't meet aggregate conditions).
Q02 of 03JUNIOR
Why does WHERE column = NULL return no rows?
ANSWER
In SQL, NULL represents an unknown value. The comparison NULL = NULL evaluates to UNKNOWN (the three-valued logic of SQL: TRUE, FALSE, UNKNOWN) — not TRUE. WHERE clauses only include rows where the condition evaluates to TRUE, so UNKNOWN rows are excluded. This is why NULL = NULL never matches. The correct syntax is WHERE column IS NULL (for finding NULLs) and WHERE column IS NOT NULL (for excluding them). IS NULL is a special operator that specifically tests for the absence of a value.
Q03 of 03JUNIOR
If you write WHERE status = 'active' OR status = 'pending' AND total > 500, which rows does this return?
ANSWER
Due to operator precedence (AND binds before OR), this evaluates as WHERE status = 'active' OR (status = 'pending' AND total > 500). It returns: all rows where status is 'active' regardless of total, PLUS rows where status is 'pending' AND total > 500. To require total > 500 for both statuses, you need parentheses: WHERE (status = 'active' OR status = 'pending') AND total > 500. This is one of the most common silent logic bugs in production SQL.
01
What is the difference between WHERE and HAVING in SQL?
JUNIOR
02
Why does WHERE column = NULL return no rows?
JUNIOR
03
If you write WHERE status = 'active' OR status = 'pending' AND total > 500, which rows does this return?
JUNIOR
FAQ · 3 QUESTIONS
Frequently Asked Questions
01
Can you use the WHERE clause without a SELECT statement?
WHERE works with SELECT, UPDATE, and DELETE — not just SELECT. In UPDATE it limits which rows get changed (UPDATE orders SET status = 'shipped' WHERE order_id = 1001), and in DELETE it limits which rows get removed. Skipping WHERE on an UPDATE or DELETE applies the change to every single row in the table, which is one of the most common catastrophic mistakes in SQL.
Was this helpful?
02
What is the difference between WHERE and HAVING in SQL?
WHERE filters individual rows before any grouping or aggregation happens. HAVING filters groups of rows after a GROUP BY has been applied. A practical rule: if you're filtering on a raw column value, use WHERE. If you're filtering on the result of an aggregate function like SUM() or COUNT(), you must use HAVING because WHERE runs too early in the query execution to see those results.
Was this helpful?
03
Does the order of conditions in a WHERE clause affect performance?
In most modern databases (PostgreSQL, MySQL, SQL Server), the query optimizer automatically reorders conditions to use indexes and run the most selective conditions first — so your hand-written order rarely matters for performance. What does matter is making sure your WHERE conditions on frequently-queried columns are indexed. An unindexed WHERE clause on a million-row table forces a full table scan regardless of how you order the conditions.