By the end of this lesson you can combine conditions with AND, OR and NOT, write a two-sided range such as 'one whole day', and translate an English request into the correct logical operator.
Why it matters: Real requests always have more than one condition: business customers in Jeddah, orders from the app placed this week, contractors hired since 2024. Getting AND and OR the right way round is the difference between the right list and an empty (or enormous) one.
Dataset: Wadi Market online store. Examples run in the editor on the right.
A WHERE clause can contain several conditions joined by logical operators:
A AND Bis true only when both A and B are true. Each AND makes the result smaller (or keeps it the same).A OR Bis true when at least one of them is true. Each OR makes the result bigger (or keeps it the same).NOT Aflips true to false and false to true.
PostgreSQL still works one row at a time: for each row it evaluates every comparison, combines the true/false results with AND, OR and NOT, and keeps the row if the final answer is true.
WHERE condition_1 AND condition_2
WHERE condition_1 OR condition_2
WHERE NOT conditionFor two conditions there are only four possibilities, so the whole behaviour fits in a small table:
| A | B | A AND B | A OR B |
|---|---|---|---|
| true | true | true | true |
| true | false | false | true |
| false | true | false | true |
| false | false | false | false |
(When a value is NULL there is a third possibility, unknown. That gets its own lesson.)
SELECT customer_id, company_name, city
FROM customers
WHERE segment = 'business'
AND city = 'Jeddah';A row is kept only if it passes both tests. Jeddah has well over a hundred customers and there are dozens of business accounts, but the rows that are both form a much shorter list. Putting each condition on its own line makes long filters easy to read.
SELECT product_name, unit_price
FROM products
WHERE unit_price < 50
OR unit_price > 3000;A product is kept if either test is true. Only a few products sit at the extremes of the price range, so the result is short, and it contains rows from both ends.
English "and" is not always SQL AND
A manager who asks for the Volt and Najm Mobile products wants products whose brand is Volt or Najm Mobile. A single product can't have both brands, so brand = 'Volt' AND brand = 'Najm Mobile' is never true and returns nothing. Ask yourself: must one row pass both tests (AND) or either test (OR)?
Two conditions on one column: ranges
AND on the same column is how you write a range. In the last lesson, ordered_at = '2025-12-31' returned nothing because it only matched midnight. A whole day is everything from midnight at its start up to but not including midnight at the start of the next day:
ordered_at >= '2025-11-28' AND ordered_at < '2025-11-29'
This pattern (inclusive start, exclusive end) is called a half-open interval. It catches every second of the day, including 23:59:59.5, and never touches the next day. You'll use it constantly.
SELECT order_id, ordered_at, channel
FROM orders
WHERE ordered_at >= '2025-11-28'
AND ordered_at < '2025-11-29';Every order placed on 28 November 2025, from 00:00:00 to the last second before midnight. White Friday is Wadi Market's busiest day of the year, so this one day holds dozens of orders.
NOT
NOT goes in front of a condition and reverses it. WHERE NOT discontinued keeps products that are still sold; WHERE NOT channel = 'web' keeps app and marketplace orders (the same as channel <> 'web'). NOT is most useful in front of a longer condition in brackets, as you'll see in the next lesson, and with operators you'll meet soon: NOT IN, NOT LIKE, IS NOT NULL.
SELECT order_id, ordered_at, channel
FROM orders
WHERE NOT channel = 'web'
AND ordered_at >= '2025-12-28';NOT applies to the comparison right after it, so the filter means: channel is not web, and the order was placed from 28 December onwards. Only app and marketplace orders from the last days of the year remain.