Multiple Conditions — AND, OR, NOT
Combine WHERE filters to answer complex business questions — precedence rules, truth tables, and every pattern you will use in production
// Part 01
Why One Condition Is Never Enough
In Module 06 you learned to filter rows with a single WHERE condition. Real business questions almost never have a single condition. They sound like this:
Every one of these questions requires combining multiple conditions. SQL provides three logical operators to do this: AND, OR, and NOT. Together they let you express any combination of conditions — no matter how complex — in a single WHERE clause.
// Part 02
AND — Both Conditions Must Be True
AND combines two conditions and returns TRUE only when both conditions are true. If either condition is false, the row is excluded. Think of AND as narrowing your results — every AND you add makes the filter more restrictive.
Truth table for AND
| Condition A | Condition B | A AND B |
|---|---|---|
| TRUE | TRUE | TRUE |
| TRUE | FALSE | FALSE |
| FALSE | TRUE | FALSE |
| FALSE | FALSE | FALSE |
| TRUE | NULL | NULL |
| FALSE | NULL | FALSE |
| NULL | NULL | NULL |
The NULL rows are important — if either condition is NULL (unknown), AND cannot determine a definitive TRUE result unless the other condition is definitively FALSE.
AND in practice — FreshMart examples
// Part 03
OR — At Least One Condition Must Be True
OR combines two conditions and returns TRUE when at least one condition is true. If both are true, the row is still included. Think of OR as broadening your results — every OR you add makes the filter more permissive, potentially returning more rows.
Truth table for OR
| Condition A | Condition B | A OR B |
|---|---|---|
| TRUE | TRUE | TRUE |
| TRUE | FALSE | TRUE |
| FALSE | TRUE | TRUE |
| FALSE | FALSE | FALSE |
| TRUE | NULL | TRUE |
| FALSE | NULL | NULL |
| NULL | NULL | NULL |
Notice that OR with NULL can still return TRUE if the other condition is TRUE. This is different from AND — when one condition is TRUE and the other is NULL, OR returns TRUE because one known TRUE is enough.
OR in practice — FreshMart examples
// Part 04
NOT — Reverse the Condition
NOT reverses the result of a condition. A condition that was TRUE becomes FALSE, and a condition that was FALSE becomes TRUE. It is the SQL equivalent of saying "everything except this."
Truth table for NOT
| Condition | NOT Condition |
|---|---|
| TRUE | FALSE |
| FALSE | TRUE |
| NULL | NULL |
Important: NOT NULL is still NULL — the absence of a value reversed is still unknown. This is why NOT IN behaves surprisingly when the list contains NULL values — you will see this in Module 15.
NOT in practice — FreshMart examples
// Part 05
Operator Precedence — The Order SQL Evaluates Conditions
When you combine AND, OR, and NOT in the same WHERE clause, the order in which they are evaluated matters enormously. SQL does not evaluate conditions left to right — it follows a precedence order, just like arithmetic (multiplication before addition).
The precedence order for logical operators is:
The classic precedence mistake
This is the most common AND/OR bug in SQL. The query looks correct but returns wrong results because AND binds before OR:
The fix — always use parentheses with OR
Parentheses override precedence completely. Anything inside parentheses is evaluated first, just like in math. When combining AND and OR, use parentheses to make your intent explicit and unambiguous.
// Part 06
Combining All Three — AND, OR, NOT Together
Complex business questions require all three operators together. The key is always: use parentheses to make evaluation order explicit. Never rely on precedence rules alone when your query mixes AND and OR.
The last query demonstrates nested parentheses — the inner parentheses group the city-tier pairs, the outer AND then applies the date filter to whichever city-tier combination matched. Reading complex WHERE clauses from the inside out (innermost parentheses first) is the most reliable way to understand what they do.
// Part 07
AND, OR, NOT with NULL — The Surprising Behaviour
NULL values interact with AND, OR, and NOT in ways that surprise beginners. The truth tables from Parts 02–04 showed the full picture — here are the practical consequences you need to know.
AND with NULL
If one condition is NULL and the other is FALSE, AND returns FALSE. If one condition is NULL and the other is TRUE, AND returns NULL (which means the row is excluded). This means a NULL condition on the AND side can cause rows to silently disappear from your results.
OR with NULL
If one condition is NULL and the other is TRUE, OR returns TRUE. This is the one place where NULL does not cause a row to disappear — the TRUE condition is enough for OR. If one condition is NULL and the other is FALSE, OR returns NULL (row excluded).
NOT with NULL
NOT NULL is still NULL. This has a major practical consequence: if you write WHERE NOT city = 'Bangalore', rows where city IS NULL will not be included in the results — even though logically you might expect "not Bangalore" to include "unknown city." NULL propagates through NOT unchanged.
🎯 Pro Tip
Whenever you write a WHERE clause that combines conditions, mentally ask: "What happens if any of the columns in my conditions contain NULL?" If a NULL value in the data would cause a row to silently disappear from your results when you expect it to be included, add an explicit IS NULL or IS NOT NULL check. Silent data loss from NULL propagation is one of the hardest bugs to spot because the query runs without error.
// Part 08
Writing Readable Multi-Condition WHERE Clauses
As conditions multiply, WHERE clauses can become hard to read. Professional SQL follows consistent formatting conventions that make complex conditions readable at a glance.
The convention of putting AND/OR at the beginning of lines (not the end) makes it easy to comment out individual conditions during debugging — just add -- in front of the line without breaking the query syntax.
// Part 09
Real Business Scenarios — Complete Examples
Here are the four business questions from Part 01 — now answered with complete queries.
Growth team — high-value customers in specific cities
Finance team — UPI orders that failed
Operations team — stock and price analysis
HR team — leadership compensation review
// Part 10
What This Looks Like at Work
You are a data analyst at CRED, a Bangalore-based fintech that rewards credit card users. The growth team calls an urgent meeting — they are preparing a targeted cashback campaign and need a segmented customer list by end of day.
🎯 Pro Tip
Always add comments to complex WHERE clauses explaining what business segment or rule each condition represents. The SQL itself shows WHAT is being filtered. The comments explain WHY — which campaign, which definition of "high-value," which date range and why. A query with good comments is a self-documenting piece of business logic that your team can run, verify, and adjust without asking you.
// Part 11
Interview Prep — 5 Questions With Complete Answers
Operator precedence determines the order in which SQL evaluates logical operators when multiple operators appear in the same WHERE clause. In SQL, NOT has the highest precedence, AND has the second highest, and OR has the lowest. This means SQL evaluates NOT conditions first, then AND conditions, and finally OR conditions — regardless of the order they appear in the query text.
This matters because mixing AND and OR without parentheses can produce results that differ from what was intended. For example: WHERE tier = 'Gold' OR tier = 'Platinum' AND city = 'Bangalore' is evaluated as WHERE tier = 'Gold' OR (tier = 'Platinum' AND city = 'Bangalore') — because AND binds before OR. This returns all Gold customers from any city plus Platinum customers from Bangalore only, rather than Gold and Platinum customers from Bangalore as the author likely intended.
The correct approach is always to use parentheses when combining AND and OR: WHERE (tier = 'Gold' OR tier = 'Platinum') AND city = 'Bangalore'. Parentheses override precedence completely — expressions inside parentheses are always evaluated first. Even when precedence would naturally produce the correct result, writing explicit parentheses makes intent clear to anyone reading the query and prevents bugs when conditions are added or modified later.
AND returns TRUE only when both conditions are true — it narrows results by requiring all conditions to be satisfied simultaneously. Every additional AND condition makes the filter more restrictive. OR returns TRUE when at least one condition is true — it broadens results by accepting rows that satisfy any of the conditions. Every additional OR condition makes the filter more permissive.
Use AND when you need rows that satisfy multiple criteria simultaneously: orders that are both delivered AND paid by UPI AND above ₹1,000. All three characteristics must be present in the same row. Use OR when you need rows that match any of several alternative criteria: customers from Bangalore OR Hyderabad OR Mumbai. A customer matching any one city qualifies.
A practical test: if you read the question with "AND" and "OR" literally, the answer is usually correct. "Give me customers from Bangalore AND with Gold tier" — AND. "Give me customers from Bangalore OR Hyderabad" — OR. "Give me Gold or Platinum customers from Bangalore" — the "or" applies to tier, so OR for tier, then AND for the city: WHERE (tier = 'Gold' OR tier = 'Platinum') AND city = 'Bangalore'.
NOT reverses the logical result of a condition. A condition that evaluates to TRUE becomes FALSE, and a condition that evaluates to FALSE becomes TRUE. NOT NULL, however, is still NULL — the unknown reversed is still unknown. This is the fundamental limitation of NOT with nullable columns.
The practical consequence: WHERE NOT city = 'Bangalore' returns rows where city is any value other than 'Bangalore' but does NOT return rows where city IS NULL. Rows with a null city silently disappear from the results. If you want to include null values in a NOT condition, you must explicitly add them: WHERE (city <> 'Bangalore' OR city IS NULL).
NOT is most commonly used in two forms: NOT LIKE (does not match a pattern), NOT IN (value not in a list), and NOT EXISTS (no matching rows in a subquery). The NOT IN form has a particularly dangerous NULL interaction — if the IN list contains even one NULL value, NOT IN returns zero rows for the entire query, which is almost never what was intended. This is why NOT EXISTS is often preferred over NOT IN for correlated subqueries. You will learn this in Module 38.
Due to operator precedence (AND before OR), this query is evaluated as: WHERE status = 'Delivered' OR (status = 'Returned' AND amount > 1000). It returns two groups of rows: all rows where status is 'Delivered' regardless of amount (every delivered order, whether ₹10 or ₹100,000), plus rows where status is 'Returned' AND amount is greater than 1,000.
This is almost certainly not what was intended. The author likely wanted all Delivered or Returned orders with amount above 1,000. The correct query uses parentheses: WHERE (status = 'Delivered' OR status = 'Returned') AND amount > 1000. The parentheses force the OR to be evaluated first, grouping the two status values, and then AND applies the amount threshold to the combined group.
This type of bug is particularly insidious because the query runs without error and returns results — just wrong results. The only way to catch it is to understand precedence rules and verify results against expected counts. Always run a SELECT COUNT(*) to verify the row count makes sense before using the results for any decision. If Delivered orders are 18 of your 30 orders, a query returning 22 rows for "delivered or returned above 1000" should immediately prompt a precedence check.
There are several correct approaches with different trade-offs. The most readable approach uses NOT with an OR group: WHERE NOT (city = 'Bangalore' OR city = 'Hyderabad' OR city = 'Mumbai'). This is evaluated as: exclude rows where city is any of the three. By De Morgan's law, this is equivalent to: WHERE city <> 'Bangalore' AND city <> 'Hyderabad' AND city <> 'Mumbai'.
A more concise and equally readable approach uses NOT IN: WHERE city NOT IN ('Bangalore', 'Hyderabad', 'Mumbai'). This is cleaner for more than two or three values. Both approaches are semantically identical and produce the same results.
The critical caveat: if the city column can contain NULL values, neither NOT with OR nor NOT IN will include rows where city is NULL — they will silently exclude them. If you want to include customers with an unknown city alongside those from non-top-three cities, add an explicit NULL check: WHERE (city NOT IN ('Bangalore', 'Hyderabad', 'Mumbai') OR city IS NULL). In the FreshMart customers table, city is defined NOT NULL, so this is not an issue — but in any table where nullable city is possible, the NULL case must be handled explicitly.
// Part 12
Errors You Will Hit — And Exactly Why They Happen
🎯 Key Takeaways
- ✓AND returns TRUE only when both conditions are true — it narrows results. Every AND makes the filter more restrictive.
- ✓OR returns TRUE when at least one condition is true — it broadens results. Every OR makes the filter more permissive.
- ✓NOT reverses a condition: TRUE becomes FALSE, FALSE becomes TRUE. NOT NULL is still NULL — rows with null values are silently excluded by NOT conditions.
- ✓Operator precedence: NOT first, AND second, OR last. SQL evaluates AND before OR in the same WHERE clause regardless of left-to-right reading order.
- ✓Always use parentheses when mixing AND and OR — even when precedence would give the correct result. Explicit parentheses prevent bugs and make intent clear.
- ✓The classic precedence bug: WHERE tier = 'Gold' OR tier = 'Platinum' AND city = 'Bangalore' is not "Gold or Platinum customers from Bangalore." AND binds first, making it: Gold (any city) OR (Platinum AND Bangalore). Fix: (tier = 'Gold' OR tier = 'Platinum') AND city = 'Bangalore'.
- ✓NOT IN returns zero rows if the list or the column contains any NULL values — because any comparison with NULL returns NULL, not FALSE. Use NOT EXISTS or add IS NOT NULL explicitly when NULL rows are possible.
- ✓Applying functions to the column side of conditions (LOWER(city) = 'bangalore') prevents index usage — a full table scan results. Standardise data on insert and use direct comparison instead.
- ✓When a NOT condition must include NULL rows, explicitly add OR column IS NULL: WHERE (city <> 'Bangalore' OR city IS NULL).
- ✓Format multi-condition WHERE clauses with one condition per line, AND/OR at the start of each line. This makes commenting out individual conditions easy during debugging.
What comes next
In Module 08, you control the order your results come back in using ORDER BY — ascending, descending, multiple columns, and how sorting interacts with NULL values.
Module 08 → Sorting Results — ORDER BYDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.