Complex WHERE — Combining Conditions
Mastering brackets, operator precedence, AND vs OR logic, multi-layer filters, and building WHERE clauses that express any business rule precisely
// Part 01
When Simple Conditions Are Not Enough
Module 06 taught you the WHERE clause with a single condition. Module 07 introduced AND, OR, and NOT. By now you have used them across dozens of queries. But real production queries combine all of these — often with four, five, or six conditions across multiple operators — and the way you structure those conditions determines whether your query is correct, readable, and maintainable.
This module is about mastering the WHERE clause at the level required for production analytics work. Not just making queries that return results — making queries that return exactly the right results, are immediately readable by anyone on your team, and do not break when data or requirements change.
The three skills this module builds:
// Part 02
Precedence — The Exact Evaluation Order
SQL evaluates operators in a fixed precedence order — just like arithmetic. Understanding this order is not optional: it is the difference between a query that is correct and one that silently returns wrong data.
| Priority | Operator / Category | Examples | Evaluated |
|---|---|---|---|
| 1st (highest) | Parentheses | (a OR b) | Always first — overrides everything |
| 2nd | Arithmetic | + - * / % | Before comparisons |
| 3rd | Comparisons | = <> < > <= >= LIKE BETWEEN IN IS NULL | Produce TRUE/FALSE/NULL |
| 4th | NOT | NOT condition | Negates the immediately following condition |
| 5th | AND | cond1 AND cond2 | Evaluated before OR |
| 6th (lowest) | OR | cond1 OR cond2 | Evaluated last — loosest binding |
The AND-before-OR rule in practice
This is the rule that causes the most production bugs. AND binds more tightly than OR. When you write A OR B AND C, the database reads it as A OR (B AND C) — not (A OR B) AND C. These produce completely different results.
Run both queries and compare the row counts. The first returns more rows — the extra rows are Gold customers from cities other than Seattle that should not appear. The second returns only what was intended.
// Part 03
Parentheses — The Tool for Explicit Grouping
Parentheses override all precedence rules. Anything inside parentheses is evaluated as a complete unit before being combined with conditions outside. They are the most important formatting tool for complex WHERE clauses — not just for correctness, but for readability.
Single level of grouping
Multiple levels of grouping
🎯 Pro Tip
A useful mental model: read your WHERE clause in English first, identify every "and" and "or" in the business rule, then map them to parenthesised groups. "I want Gold or Platinum customers from Seattle who joined after 2021" breaks down as: (Gold OR Platinum) AND Seattle AND joined after 2021. Write the parentheses to match the English groupings, not to match the left-to-right order of the words.
// Part 04
AND vs OR — Narrowing vs Broadening
The mental model for AND and OR is simple but important to internalise deeply for complex queries.
AND narrows. Every AND condition you add reduces the number of rows returned. A row must satisfy ALL AND conditions to be included. Adding more AND conditions can only keep or reduce the result — never increase it.
OR broadens. Every OR condition you add potentially increases the number of rows returned. A row needs to satisfy ANY ONE OR condition to be included. Adding more OR conditions can only keep or increase the result — never reduce it.
Using the narrowing/broadening model to debug
When your query returns too many rows, look for OR conditions that should be AND. When it returns too few rows, look for AND conditions that should be OR — or for missing parentheses that are causing an AND to steal from your OR group.
// Part 05
De Morgan's Laws — Rewriting NOT Conditions
De Morgan's Laws are logical identities that let you rewrite NOT expressions. They are useful when you want to simplify a complex NOT condition or convert between equivalent forms.
All three produce the same result. Choose whichever reads most clearly for the specific condition. NOT IN is usually the most readable for lists. NOT (A OR B) is clearest when you want to emphasise "not any of these." The De Morgan equivalent is useful when you want to write positive conditions rather than negative ones.
// Part 06
Translating Business Rules Into WHERE Clauses
The most valuable skill in this module is the ability to read a business requirement written in plain English and translate it directly into a correctly structured WHERE clause. Here is the systematic process.
Step 1 — identify the entities and their conditions
Read the requirement and underline every condition. Each condition becomes a comparison expression. The words "and", "or", and "not" become AND, OR, and NOT. The words "either...or", "any of", and "one of" indicate OR. The words "both", "all of", "must also" indicate AND.
Step 2 — identify the groupings
Look for conditions that belong together as a unit — connected by OR within a larger AND structure. These become parenthesised groups. Read the requirement in English and identify where the "and" applies to the whole phrase versus just the last part.
Step 3 — write the WHERE clause with explicit parentheses
Write each group in parentheses. Connect groups with AND or OR. Always use parentheses even when precedence would give the correct result — explicit is always better than implicit.
Worked examples
Business Rule 01
"Find all delivered orders paid by UPI OR Card, where the total is above ₹500, placed in January or February 2024."
Groups: (UPI OR Card) AND total > 500 AND (Jan OR Feb 2024)
Business Rule 02
"Find products that either: (a) are out of stock AND have a margin above 25%, OR (b) are in stock AND priced below ₹30."
Groups: (out-of-stock AND high-margin) OR (in-stock AND cheap)
Business Rule 03
"Find employees who are either Store Managers with salary above ₹50,000, OR Cashiers in Seattle stores, OR any employee hired before 2020 earning more than ₹60,000."
Three OR groups, each group has internal AND conditions
// Part 07
Formatting Complex WHERE Clauses for Readability
A WHERE clause that is logically correct but unreadable is a maintenance liability. Six months from now, a colleague — or you — will need to modify it, and an unreadable WHERE clause gets misread and broken. Professional SQL formatting makes complex conditions immediately scannable.
The conventions
Before and after — the same logic, two readability levels
The second version takes 5 seconds longer to write and saves 5 minutes every time someone reads or modifies it. In a shared codebase or data team, the formatted version is the only acceptable standard.
// Part 08
Debugging Complex WHERE Clauses
When a complex WHERE returns wrong results — too many rows, too few rows, or rows that should not be there — here is the systematic debugging process.
Step 1 — verify the row count expectation
Before you can say a result is wrong, you need to know what is right. Run COUNT(*) with a mental estimate: "I expect about 8 rows from this filter." If you get 15, the filter is too broad. If you get 2, it is too narrow.
Step 2 — test each condition group in isolation
Replace the full WHERE with each group independently and check how many rows each group returns. This identifies which group is misbehaving.
Step 3 — add SELECT columns to see what is being included
Remove any LIMIT and look at the actual rows being returned. Compare them against your expectation. A row that should not be there tells you which condition is too permissive.
Step 4 — add conditions one at a time
Start with the simplest version of the WHERE clause and add conditions one by one, checking the row count after each addition. The count should narrow monotonically as you add AND conditions. If it unexpectedly broadens, you have accidentally used OR when you meant AND.
🎯 Pro Tip
The fastest debugging technique for a complex WHERE: temporarily replace the SELECT columns with SELECT *, remove LIMIT, and scan the results for any row that should obviously not be there. A Gold customer from Delhi appearing in a "Platinum Seattle customers" query tells you immediately that the loyalty_tier condition or the city condition has a precedence bug. One wrong row is usually enough to identify the broken condition.
// Part 09
Short-Circuit Evaluation and Performance
SQL databases use short-circuit evaluation for WHERE conditions — once the result of an expression is determined, remaining conditions may not be evaluated. This has performance implications for complex WHERE clauses.
AND short-circuit — FALSE stops evaluation
For AND conditions, if any condition is FALSE, the entire AND expression is FALSE — remaining conditions do not need to be evaluated. The database typically evaluates the cheapest or most selective condition first (when conditions are independent), using query optimiser statistics to determine which condition eliminates the most rows.
OR short-circuit — TRUE stops evaluation
For OR conditions, if any condition is TRUE, the entire OR expression is TRUE — remaining conditions are skipped. Put the most commonly satisfied condition first in an OR chain.
Ordering conditions for performance
While the query optimiser handles most reordering automatically, these manual guidelines help when the optimiser cannot reorder (due to side effects or complex expressions):
// Part 10
Real Production WHERE Clauses
Here are complete, production-quality complex WHERE clauses with real business context.
Fraud detection — suspicious order pattern
Campaign targeting — multi-segment customer list
Inventory alert — multi-condition product filter
// Part 11
What This Looks Like at Work
You are an analyst at Venmo, India's largest UPI payments platform. The risk team sends a request: they need a list of transactions matching a specific suspicious pattern for manual review. The pattern has multiple conditions across three dimensions — amount, timing, and transaction type.
// Part 12
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 without explicit parentheses. The order is: parentheses first (highest), then arithmetic operators, then comparison operators (=, <>, LIKE, BETWEEN, IN, IS NULL), then NOT, then AND, and finally OR (lowest). This means AND binds more tightly than OR.
The most common precedence bug arises when mixing AND and OR without parentheses. WHERE tier = 'Gold' OR tier = 'Platinum' AND city = 'Seattle' is evaluated as WHERE tier = 'Gold' OR (tier = 'Platinum' AND city = 'Seattle') — because AND is evaluated before OR. This returns all Gold customers from any city, plus Platinum customers only from Seattle. The intended query — Gold or Platinum customers from Seattle — requires explicit parentheses: WHERE (tier = 'Gold' OR tier = 'Platinum') AND city = 'Seattle'.
The bug is insidious because the query runs without error and returns results that look plausible — it just returns more rows than intended. Without knowing the exact expected count, the bug is invisible. The prevention is simple: always use explicit parentheses when mixing AND and OR in the same WHERE clause. Even when precedence would give the correct result, parentheses make the intent clear and prevent future bugs when conditions are modified.
The systematic process: first, read the business rule in plain English and identify every condition. Each condition becomes a comparison expression. The words "and", "both", "must also" map to AND. The words "or", "either...or", "any of" map to OR. The word "not" maps to NOT.
Second, identify the groupings — which conditions belong together as a unit before being combined with others. In the rule "Gold or Platinum customers from Seattle who joined after 2021," the "from Seattle" and "joined after 2021" apply to the combined (Gold OR Platinum) group — not just to Platinum. The parenthesised structure is: (Gold OR Platinum) AND Seattle AND after 2021.
Third, write the WHERE clause with explicit parentheses matching the English groupings: WHERE (loyalty_tier = 'Gold' OR loyalty_tier = 'Platinum') AND city = 'Seattle' AND joined_date >= '2022-01-01'. Fourth, verify by running COUNT(*) with each condition group in isolation, then combined, and checking that each count makes sense. A quick sanity check: does each row in the result satisfy all the conditions described in the business rule? Sample 5 rows and verify manually. This four-step process — identify, group, write, verify — works for any business rule regardless of complexity.
De Morgan's Laws are logical identities that describe how NOT distributes over AND and OR. Law 1: NOT (A OR B) equals NOT A AND NOT B. Law 2: NOT (A AND B) equals NOT A OR NOT B. These identities mean you can always rewrite a NOT expression over a group by distributing the NOT into each sub-expression and flipping OR to AND (or AND to OR).
In SQL, De Morgan's Laws explain the equivalence between different ways of writing exclusion conditions. NOT IN (a, b, c) is exactly De Morgan applied: it expands to NOT (col = a OR col = b OR col = c), which by De Morgan equals col <> a AND col <> b AND col <> c. This also explains why NOT IN fails when the list contains NULL — the col <> NULL term always evaluates to NULL, making the entire AND expression NULL for every row.
Practical uses in SQL: simplifying double negatives (NOT (NOT condition) = condition), rewriting NOT IN as explicit AND chains for clarity or portability, and understanding why NOT with OR behaves differently than expected. When debugging a NOT condition that excludes more rows than expected, apply De Morgan to rewrite it in positive terms — the positive form is usually easier to reason about. WHERE NOT (status = 'Cancelled' OR status = 'Returned') rewritten as WHERE status <> 'Cancelled' AND status <> 'Returned' makes it immediately clear which values are excluded.
The systematic debugging process starts with establishing the expected count. Before declaring the result wrong, calculate what you expect: "This table has 30 orders. About 18 are delivered. Of those, maybe 8 were paid by UPI. Of those, maybe 4 were above ₹500." If the query returns 12 instead of 4, the filter is too broad.
Next, test each condition group in isolation. Replace the full WHERE with just the first group and run COUNT(*). Then test the second group alone. Then combine them. For AND chains, each added condition should reduce or maintain the count. If adding a condition increases the count, you accidentally wrote OR. For OR chains, each added alternative should increase or maintain the count. If adding an OR alternative reduces the count, you have a precedence issue where AND is stealing from your OR group.
The most effective visual debugging technique: SELECT * from the full query (remove LIMIT) and scan the results for any row that obviously should not be there. A Gold customer from Delhi in a "Platinum Seattle customers" result immediately identifies a missing parenthesis around the loyalty tier OR condition. One wrong row tells you more than a count discrepancy because you can see exactly which condition failed for that specific row. Once identified, fix the condition, rerun, and verify the count matches the expectation before closing the debug session.
A single WHERE clause is appropriate when all conditions apply directly to columns in the available tables, the conditions are logically parallel (all ANDs, or a flat list of ORs), and the number of conditions is manageable — typically fewer than six to eight conditions. Single-level WHERE clauses with good formatting remain readable and maintainable up to this complexity.
A subquery or CTE (WITH clause) is better when: you need to filter on the result of an aggregation (WHERE total_orders > 5 — requires counting first), the filter depends on a derived value that appears in multiple conditions (defining margin_pct once and using it in two conditions avoids repeating the expression), or the logic has two clearly distinct phases (first identify the qualifying customers, then get their orders) that would be clearer as separate steps.
A CTE specifically helps when the same complex condition needs to be reused — define it once in WITH and reference it cleanly in the main query. It also helps when the WHERE conditions are so numerous that even well-formatted parentheses become hard to follow. As a practical rule: if you cannot explain what your WHERE clause does in one sentence, consider whether a CTE would make the steps explicit and self-documenting. The goal is always a query that any analyst on your team can read and immediately understand — formatting, parentheses, and CTE decomposition are all tools toward that goal.
// Part 13
Errors You Will Hit — And Exactly Why They Happen
🎯 Key Takeaways
- ✓Operator precedence order: parentheses → arithmetic → comparisons → NOT → AND → OR. AND always evaluates before OR when no parentheses are present.
- ✓The most common production bug: WHERE tier = 'Gold' OR tier = 'Platinum' AND city = 'Seattle' is not "(Gold OR Platinum) AND Seattle" — AND runs first, making it "Gold (any city) OR (Platinum AND Seattle)."
- ✓Always use explicit parentheses when mixing AND and OR. Even when precedence gives the correct answer, parentheses communicate intent and prevent future bugs when conditions are modified.
- ✓AND narrows: every added AND can only reduce or maintain the result count. OR broadens: every added OR can only increase or maintain the result count. If adding AND increases the count, you have an OR where you meant AND.
- ✓De Morgan's Laws: NOT (A OR B) = NOT A AND NOT B. NOT (A AND B) = NOT A OR NOT B. Use these to rewrite NOT conditions into equivalent positive forms for clarity.
- ✓Translate business rules systematically: identify every condition, map "and/both/must" to AND and "or/either/any" to OR, identify which conditions group together, then write WITH explicit parentheses matching the English groupings.
- ✓Format WHERE clauses professionally: one condition per line, AND/OR at the start of each line, indented groups, comments above the WHERE block describing the business rule.
- ✓Debug by isolation: test each condition group alone with COUNT(*), then combine. For AND chains, count should decrease with each condition added. If it increases, you have an OR bug.
- ✓NOT conditions silently exclude NULL rows — any NULL in the column being compared evaluates the NOT condition to NULL, which WHERE discards. Add OR column IS NULL explicitly when NULL rows should be included in NOT results.
- ✓Multiple OR conditions across different columns can prevent index optimisation. If an OR-heavy query is slow, consider UNION ALL (each branch uses its own index) or restructure with EXISTS subqueries.
What comes next
In Module 18, you learn SQL data types — what types exist, which to use for each situation, and how wrong type choices cause silent bugs in calculations, comparisons, and joins.
Module 18 → SQL Data TypesDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.