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.

Beginner75–95 minutesBoolean logic + verification labLast reviewed: August 2026

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.

01

Combine predicates with AND, OR, and NOT.

02

Apply SQL operator precedence and use parentheses to make intent explicit.

03

Refactor logical conditions using De Morgan’s laws without changing results.

04

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.

sqlite · combine filters
SELECT    customer_id,    full_name,    city,    segmentFROM customerWHERE segment = 'consumer'  AND city = 'Tehran';

Operator precedence

Across mainstream SQL dialects, logical precedence is generally:

NOT evaluated first
AND evaluated next
OR evaluated last

Parentheses can override this order and should be used whenever a reader could misinterpret the requirement.

sql · implicit precedence
-- 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

sql · two different requirements
-- 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

ABA AND BA OR B
TRUETRUETRUETRUE
TRUEFALSEFALSETRUE
FALSETRUEFALSETRUE
FALSEFALSEFALSEFALSE

SQL adds UNKNOWN when NULL participates. In a WHERE clause, both FALSE and UNKNOWN are rejected.

ExpressionResult
TRUE AND UNKNOWNUNKNOWN
FALSE AND UNKNOWNFALSE
TRUE OR UNKNOWNTRUE
FALSE OR UNKNOWNUNKNOWN
NOT UNKNOWNUNKNOWN

NULL changes apparently simple negation

sqlite · not equal does not include NULL
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.

sqlite · include missing cities explicitly
SELECT customer_id, full_name, cityFROM customerWHERE city <> 'Berlin'   OR city IS NULL;

De Morgan’s laws

Two reliable equivalences help simplify negated groups:

OriginalEquivalent
NOT (A AND B)(NOT A) OR (NOT B)
NOT (A OR B)(NOT A) AND (NOT B)
sql · exclude two cities
-- Clear direct formWHERE NOT (city = 'Berlin' OR city = 'Tehran')-- Equivalent for non-NULL city valuesWHERE city <> 'Berlin'  AND city <> 'Tehran';
Remember UNKNOWN

These transformations preserve three-valued logic, but a final WHERE still rejects UNKNOWN. Decide separately whether NULL should qualify.

Guard conditions and readable ordering

sqlite · filter calculated values safely
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

Write one predicate
Run and inspect rows
Add the second predicate
Verify set difference
Add grouping
Test boundary and NULL rows

Incremental verification makes it easier to identify the exact condition that removes or adds unexpected rows.

sqlite · staged verification
-- 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

  1. Return consumers in Berlin or Tehran.
  2. Return products that are books or labs and cost less than 30.
  3. Return sales with quantity at least 2 unless the discount is 20% or more.
  4. Return customers not in Berlin, including customers with an unknown city.
  5. Write two differently parenthesized predicates that use the same conditions but return different rows.
sqlite · possible solutions
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

  1. Which is evaluated first: AND or OR?
  2. Does NOT UNKNOWN become TRUE?
  3. How would you express “consumer customers in Berlin or Tehran”?
  4. 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

  • NOT binds more tightly than AND, which binds more tightly than OR.
  • Parentheses document and protect business meaning.
  • SQL logical expressions can produce UNKNOWN.
  • Test compound filters incrementally and include NULL cases.

References

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.