Chapter 05 · Filtering, Sorting, and Limiting Results
AND, OR, NOT, and Operator Precedence
Build compound filters that remain correct when requirements grow, conditions overlap, and missing values enter the data.
Learning outcomes
Real requirements rarely fit one comparison. Compound predicates combine smaller conditions, and correctness depends on precedence, parentheses, and SQL’s three-valued logic.
Combine predicates with AND, OR, and NOT.
Apply SQL operator precedence and use parentheses to make intent explicit.
Refactor logical conditions using De Morgan’s laws without changing results.
Test compound predicates with truth tables and boundary-focused sample data.
The three logical operators
AND
The row qualifies only when both component predicates are TRUE.
OR
The row qualifies when at least one component predicate is TRUE.
NOT
Negates a predicate, turning TRUE into FALSE and FALSE into TRUE; UNKNOWN remains UNKNOWN.
Parentheses
Group conditions explicitly and protect meaning during later edits.
SELECT customer_id, full_name, city, segmentFROM customerWHERE segment = 'consumer' AND city = 'Tehran';Operator precedence
Across mainstream SQL dialects, logical precedence is generally:
Parentheses can override this order and should be used whenever a reader could misinterpret the requirement.
-- Interpreted as:-- segment = 'business'-- OR (city = 'Tehran' AND city IS NOT NULL)SELECT customer_id, full_name, city, segmentFROM customerWHERE segment = 'business' OR city = 'Tehran' AND city IS NOT NULL;The city IS NOT NULL condition applies only to the second branch because AND binds more tightly than OR.
Parentheses make business rules visible
-- Requirement A:-- Any business customer, plus Tehran consumers.WHERE segment = 'business' OR (segment = 'consumer' AND city = 'Tehran')-- Requirement B:-- Customers in Tehran who are either segment.WHERE (segment = 'business' OR segment = 'consumer') AND city = 'Tehran';The same words and operators can produce different sets of rows. Group the condition around the requirement, not around visual convenience.
Truth tables for two-valued cases
| A | B | A AND B | A OR B |
|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE |
| TRUE | FALSE | FALSE | TRUE |
| FALSE | TRUE | FALSE | TRUE |
| FALSE | FALSE | FALSE | FALSE |
SQL adds UNKNOWN when NULL participates. In a WHERE clause, both FALSE and UNKNOWN are rejected.
| Expression | Result |
|---|---|
TRUE AND UNKNOWN | UNKNOWN |
FALSE AND UNKNOWN | FALSE |
TRUE OR UNKNOWN | TRUE |
FALSE OR UNKNOWN | UNKNOWN |
NOT UNKNOWN | UNKNOWN |
NULL changes apparently simple negation
SELECT customer_id, full_name, cityFROM customerWHERE city <> 'Berlin';This excludes Berlin rows, but it also excludes rows where city is NULL because the comparison is UNKNOWN.
SELECT customer_id, full_name, cityFROM customerWHERE city <> 'Berlin' OR city IS NULL;De Morgan’s laws
Two reliable equivalences help simplify negated groups:
| Original | Equivalent |
|---|---|
NOT (A AND B) | (NOT A) OR (NOT B) |
NOT (A OR B) | (NOT A) AND (NOT B) |
-- Clear direct formWHERE NOT (city = 'Berlin' OR city = 'Tehran')-- Equivalent for non-NULL city valuesWHERE city <> 'Berlin' AND city <> 'Tehran';These transformations preserve three-valued logic, but a final WHERE still rejects UNKNOWN. Decide separately whether NULL should qualify.
Guard conditions and readable ordering
SELECT s.sale_id, s.quantity, p.unit_price, s.discount_rateFROM sale AS sJOIN product AS p ON p.product_id = s.product_idWHERE s.quantity >= 2 AND p.unit_price >= 20 AND s.discount_rate < 0.20;Place related conditions together and format one major predicate per line. SQL does not guarantee short-circuit evaluation in the same way as many programming languages; do not depend on condition order to prevent an invalid expression.
Build complex filters incrementally
Incremental verification makes it easier to identify the exact condition that removes or adds unexpected rows.
-- Stage 1: all course productsSELECT product_id, product_name, unit_priceFROM productWHERE category = 'course';-- Stage 2: course products in the target price bandSELECT product_id, product_name, unit_priceFROM productWHERE category = 'course' AND unit_price >= 50 AND unit_price < 90;Practice lab
- Return consumers in Berlin or Tehran.
- Return products that are books or labs and cost less than 30.
- Return sales with quantity at least 2 unless the discount is 20% or more.
- Return customers not in Berlin, including customers with an unknown city.
- Write two differently parenthesized predicates that use the same conditions but return different rows.
SELECT customer_id, full_name, cityFROM customerWHERE segment = 'consumer' AND (city = 'Berlin' OR city = 'Tehran');SELECT product_id, product_name, category, unit_priceFROM productWHERE (category = 'book' OR category = 'lab') AND unit_price < 30;SELECT sale_id, quantity, discount_rateFROM saleWHERE quantity >= 2 AND NOT (discount_rate >= 0.20);SELECT customer_id, full_name, cityFROM customerWHERE city <> 'Berlin' OR city IS NULL;Common failures
Mixing AND and OR without parentheses
The query may be syntactically valid while expressing a different business rule.
Assuming NOT includes NULL rows
Negating UNKNOWN still produces UNKNOWN.
Depending on left-to-right evaluation
SQL is declarative. Write expressions that are valid independently of optimizer evaluation order.
Changing several conditions before retesting
Compound predicate bugs are easier to isolate incrementally.
Checkpoint
Reason before running
- Which is evaluated first: AND or OR?
- Does NOT UNKNOWN become TRUE?
- How would you express “consumer customers in Berlin or Tehran”?
- Why can city <> 'Berlin' omit rows unexpectedly?
Review the answers
AND precedes OR; NOT UNKNOWN remains UNKNOWN; group the city alternatives inside parentheses; an ordinary comparison with NULL is UNKNOWN and therefore rejected by WHERE.
Summary and references
NOTbinds more tightly thanAND, which binds more tightly thanOR.- Parentheses document and protect business meaning.
- SQL logical expressions can produce UNKNOWN.
- Test compound filters incrementally and include NULL cases.