PostgreSQL WHERE Operators

Master the WHERE clause with operators in PostgreSQL. Learn comparison and logical operators, filtering techniques, and practical examples to refine your SQL queries.

Focus: use the where clause with operators

Sponsored

You’ve written a dozen queries that return whole tables, but now your real-world data is piling up—thousands of orders, millions of log lines—and you need only the rows that matter. Without a precise WHERE clause with operators, you’re left scrolling through noise, or worse, pulling too much data into memory and slowing your application. In this lesson, you’ll learn to filter data like a pro using PostgreSQL’s comparison and logical operators, so your queries become faster, safer, and more expressive.

The problem this lesson solves

Raw SELECT statements are blunt instruments: SELECT * FROM orders; returns every column of every row. When you’re debugging a production issue or building a dashboard, that’s not just unwieldy—it’s a performance hazard. The WHERE clause is your scalpel, letting you surgically extract only the rows that meet specific conditions. Without a solid grasp of operators, you’ll either under-filter (fetching too much) or over-filter (missing critical data). This lesson gives you the exact syntax and mental models to filter confidently.

Pro tip: Filtering early in your query reduces network transfer and memory usage—essential when your tables grow to millions of rows.

Core concept / mental model

Think of WHERE as a gatekeeper at a club. Each row in your table walks up to the door, and the gatekeeper checks the row against a bouncer’s checklist—your conditions. If the row passes, it’s let in; otherwise, it’s turned away. Operators are the rules of the checklist: “age is at least 21” (comparison), “VIP or guest” (logical OR), “not on the banned list” (negation).

Key terms to know:

  • Comparison operators: =, != or <>, >, <, >=, <= — compare values.
  • Logical operators: AND, OR, NOT — combine or negate conditions.
  • Pattern matching: LIKE, ILIKE, SIMILAR TO — fuzzy matches with wildcards.
  • Range and membership: BETWEEN, IN — shorthand for multiple comparisons.

The WHERE clause is evaluated after the FROM clause but before GROUP BY, HAVING, and ORDER BY. That means the filter happens early in the query pipeline, which is why it has such a big impact on performance.

How it works step by step

  1. Start with a base query: SELECT column1, column2 FROM table_name;
  2. Add the WHERE keyword: Place it immediately after the table name.
  3. Write a condition: Use an operator to compare a column (or expression) with a value or another column.
  4. Combine conditions: Use AND, OR, and NOT to build complex logic. Remember, AND binds tighter than OR, just like multiplication before addition in arithmetic.
  5. Test and refine: Run the query, inspect the result set, and adjust. If you get too many rows, add more conditions; if too few, loosen them.

Operator precedence

PostgreSQL follows standard SQL precedence:

  1. = , !=, <>, <, >, <=, >=
  2. NOT
  3. AND
  4. OR

So a OR b AND c means a OR (b AND c). Use parentheses to make the intent explicit and avoid bugs.

Hands-on walkthrough

Let’s apply this with a sample products table. First, create and populate it:

-- Create a simple products table
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    price NUMERIC(10, 2) NOT NULL,
    category TEXT,
    in_stock BOOLEAN DEFAULT true
);

-- Insert sample data
INSERT INTO products (name, price, category, in_stock) VALUES
('Widget A', 19.99, 'Tools', true),
('Widget B', 34.50, 'Tools', false),
('Gadget X', 12.00, 'Gadgets', true),
('Gadget Y', 99.99, 'Gadgets', true),
('Accessory Z', 5.75, 'Accessories', false);

Now, filter using comparison operators:

-- Find products that cost less than $20
SELECT name, price FROM products WHERE price < 20;

Output:

    name    | price
------------+-------
 Widget A   | 19.99
 Gadget X   | 12.00
 Accessory Z|  5.75

Combine with logical operators:

-- Products that are in stock AND cost at least $10
SELECT name, price, in_stock
FROM products
WHERE in_stock = true AND price >= 10;

Output:

   name   | price | in_stock
----------+-------+----------
 Widget A | 19.99 | t
 Gadget X | 12.00 | t
 Gadget Y | 99.99 | t

Use IN and BETWEEN for cleaner syntax:

-- Products in the Tools or Gadgets category with price between 10 and 50
SELECT name, category, price
FROM products
WHERE category IN ('Tools', 'Gadgets')
  AND price BETWEEN 10 AND 50;

Output:

   name   | category | price
----------+----------+-------
 Widget A | Tools    | 19.99
 Widget B | Tools    | 34.50
 Gadget X | Gadgets  | 12.00

Compare options / when to choose what

Operator Use case Example When to prefer
= / != Exact match WHERE status = 'active' Quick, precise filtering on discrete values
<, >, <=, >= Range comparisons WHERE age >= 18 Numeric or date thresholds
BETWEEN Inclusive range (shorthand) WHERE price BETWEEN 10 AND 50 Clean, readable range tests
IN Multiple discrete values WHERE category IN ('A', 'B') Avoid long OR chains
LIKE / ILIKE Pattern matching WHERE name LIKE 'Widget%' Substring or prefix/suffix searches (ILIKE is case-insensitive)
AND / OR / NOT Combine logic WHERE active = true AND age > 21 Building complex filters

When to choose what: Use = for exact matches, BETWEEN for inclusive numeric ranges, IN for small lists of known values, and ILIKE when case-insensitive text search is needed. For high-performance text search, consider full-text search (not covered here) instead of LIKE with leading wildcards.

Troubleshooting & edge cases

  • NULL values: WHERE column = NULL never returns rows. Use IS NULL or IS NOT NULL instead. For example, WHERE end_date IS NULL finds open-ended records.
  • Precedence surprises: WHERE active = true OR role = 'admin' AND age > 30 is interpreted as active = true OR (role = 'admin' AND age > 30), which might not be what you intended. Always parenthesize AND/OR groups.
  • BETWEEN is inclusive: BETWEEN 10 AND 20 includes 10 and 20. If you need exclusive bounds, use > 10 AND < 20.
  • Case sensitivity: = is case-sensitive. WHERE name = 'widget' won’t match 'Widget'. Use ILIKE or lower() if needed (but beware performance implications).
  • Data type mismatches: Comparing a text column to a number can cause errors or implicit casts. Ensure types align.

What you learned & what's next

You now understand how to use the WHERE clause with operators to filter rows in PostgreSQL. You learned comparison operators for exact and range checks, logical operators to combine conditions, IN and BETWEEN for concise syntax, and you saw practical examples that you can run yourself. You also know how to avoid common pitfalls like NULL handling and precedence issues.

You’ve met the learning objectives: you can explain the core idea behind the WHERE clause with operators and complete a practical filtering exercise. Next in the track, you’ll build on this foundation by exploring sorting and limiting results with ORDER BY and LIMIT, which will help you control the shape and volume of your query output even further.

Practice recap

Try this: write a query on the products table that returns the names of products that are in stock and priced between $15 and $50, or that are in the 'Gadgets' category. Then modify it to exclude any product whose name starts with 'Gadget' using NOT LIKE. Run each version and compare the outputs — this will solidify your understanding of combining operators.

Common mistakes

  • Using = NULL instead of IS NULL — this returns no rows because NULL is not equal to anything.
  • Forgetting parentheses when mixing AND and OR — the result can be logically different from your intention.
  • Assuming BETWEEN is exclusive — it includes both endpoints, which can lead to off-by-one errors.
  • Making the query case-sensitive with = when you really need ILIKE for text matching.

Variations

  1. Instead of IN, you can use = ANY(ARRAY[...]) — it behaves similarly and can be useful with subqueries.
  2. For date ranges, BETWEEN is handy, but for open-ended ranges, >= and < with DATE_TRUNC or NOW() give more control.
  3. Use ILIKE for case-insensitive pattern matching, which is an alternative to lowercasing columns or values with LOWER().

Real-world use cases

  • Filtering e-commerce orders to show only those placed in the last 30 days using WHERE order_date > NOW() - INTERVAL '30 days'
  • Finding all active users in a database who haven't logged in for over a week with WHERE active = true AND last_login < NOW() - INTERVAL '7 days'
  • Extracting products in specific categories with a price range for a promotional campaign, using WHERE category IN ('Electronics', 'Accessories') AND price BETWEEN 10 AND 100

Key takeaways

  • The WHERE clause filters rows after the FROM clause and before grouping, making it the primary tool for narrowing result sets.
  • Comparison operators (=, !=, <, >, <=, >=) handle exact and range matching; BETWEEN is a shorthand for inclusive ranges.
  • Logical operators AND, OR, NOT combine conditions — remember that AND binds tighter than OR, and use parentheses to avoid ambiguity.
  • IN and ILIKE provide concise alternatives to long OR chains and case-sensitive LIKE, respectively.
  • NULL comparisons always evaluate to unknown; always use IS NULL or IS NOT NULL to check for NULL.
  • Testing your query incrementally and checking the result set against your expectations prevents subtle filtering bugs.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.