Learning Guides
Menu

Filtering Data

9 min readLearning SQL

Filtering Data

Retrieving every row from a table is rarely useful. The WHERE clause lets you filter rows based on conditions, returning only the data you actually need.

The WHERE Clause

Add WHERE after FROM to filter results:

SQL
SELECT title, price, stock_quantity
FROM books
WHERE price > 15;
titlepricestock_quantity
198415.9945
One Hundred Years of Solitude18.9920
Norwegian Wood16.9935
Kafka on the Shore17.9928

The database evaluates the condition for each row and includes only rows where the condition is true.

Comparison Operators

Basic Comparisons

OperatorMeaningExample
=Equal toprice = 15.99
<> or !=Not equal tostatus <> 'cancelled'
<Less thanstock_quantity < 10
>Greater thanprice > 20
<=Less than or equal topublication_year <= 2000
>=Greater than or equal toprice >= 15
SQL
-- Books published in or after 1950
SELECT title, publication_year
FROM books
WHERE publication_year >= 1950;
 
-- Orders that aren't pending
SELECT order_id, status
FROM orders
WHERE status <> 'pending';

String Comparisons

Strings are compared alphabetically:

SQL
-- Authors whose last name comes before 'M' alphabetically
SELECT first_name, last_name
FROM authors
WHERE last_name < 'M';
first_namelast_name
JaneAusten
GabrielGarcía Márquez

Note

String comparison behavior (case sensitivity, collation) varies between databases. MySQL is case-insensitive by default; PostgreSQL is case-sensitive.

Date Comparisons

Dates work naturally with comparison operators:

SQL
-- Orders placed in January 2024
SELECT order_id, order_date
FROM orders
WHERE order_date >= '2024-01-01'
  AND order_date < '2024-02-01';

Range Conditions with BETWEEN

BETWEEN checks if a value falls within a range (inclusive):

SQL
-- Books priced between $14 and $17
SELECT title, price
FROM books
WHERE price BETWEEN 14 AND 17;
titleprice
Pride and Prejudice14.99
198415.99
Norwegian Wood16.99

BETWEEN is inclusive on both ends. It's equivalent to:

SQL
WHERE price >= 14 AND price <= 17

Warning

For dates, BETWEEN can be tricky. BETWEEN '2024-01-01' AND '2024-01-31' won't include events on January 31st if they have a time component. Use explicit comparisons for dates with times.

NOT BETWEEN

SQL
-- Books NOT in the $14-17 range
SELECT title, price
FROM books
WHERE price NOT BETWEEN 14 AND 17;

Membership with IN

IN checks if a value matches any item in a list:

SQL
-- Books by authors 1 or 4
SELECT title, author_id
FROM books
WHERE author_id IN (1, 4);
titleauthor_id
19841
Animal Farm1
Norwegian Wood4
Kafka on the Shore4

This is equivalent to multiple OR conditions:

SQL
WHERE author_id = 1 OR author_id = 4

NOT IN

SQL
-- Orders that aren't pending or cancelled
SELECT order_id, status
FROM orders
WHERE status NOT IN ('pending', 'cancelled');

Note

IN lists can contain subqueries, which we'll cover in the Subqueries chapter. This makes IN extremely powerful for complex filtering.

Pattern Matching with LIKE

LIKE matches strings against patterns using wildcards:

WildcardMeaningExample
%Any sequence of characters'%wood'
_Any single character'_984'
SQL
-- Books with 'Wood' in the title
SELECT title FROM books WHERE title LIKE '%Wood%';
title
Norwegian Wood
SQL
-- Books starting with 'A'
SELECT title FROM books WHERE title LIKE 'A%';
title
Animal Farm
SQL
-- Email addresses from email.com domain
SELECT email FROM customers WHERE email LIKE '%@email.com';

Common Patterns

SQL
-- Starts with 'John'
WHERE name LIKE 'John%'
 
-- Ends with 'son'
WHERE name LIKE '%son'
 
-- Contains 'mark'
WHERE name LIKE '%mark%'
 
-- Second character is 'a'
WHERE name LIKE '_a%'
 
-- Exactly 5 characters
WHERE code LIKE '_____'

Case Sensitivity

LIKE behavior varies:

  • MySQL: Case-insensitive by default
  • PostgreSQL: Case-sensitive (use ILIKE for case-insensitive)
SQL
-- PostgreSQL case-insensitive search
SELECT title FROM books WHERE title ILIKE '%the%';

Escaping Wildcards

To match literal % or _ characters:

SQL
-- Find values containing a literal %
SELECT * FROM products WHERE name LIKE '%\%%' ESCAPE '\';

Working with NULL

NULL requires special treatment because NULL represents "unknown." Comparisons with NULL always yield unknown (neither true nor false).

SQL
-- This finds nothing, even if birth_date is NULL
SELECT * FROM authors WHERE birth_date = NULL;  -- WRONG!
 
-- Correct way to find NULLs
SELECT * FROM authors WHERE birth_date IS NULL;
 
-- Find non-NULL values
SELECT * FROM authors WHERE birth_date IS NOT NULL;

NULL Comparison Gotcha

Consider this data:

author_idnamebirth_date
1Alice1980-05-15
2BobNULL
SQL
-- This returns only Alice, not Bob
SELECT * FROM authors WHERE birth_date <> '1990-01-01';
 
-- To include Bob, you must explicitly check for NULL
SELECT * FROM authors
WHERE birth_date <> '1990-01-01' OR birth_date IS NULL;

Logical Operators

Combine conditions with AND, OR, and NOT.

AND - All Conditions Must Be True

SQL
-- Expensive books with good stock
SELECT title, price, stock_quantity
FROM books
WHERE price > 15 AND stock_quantity > 25;
titlepricestock_quantity
198415.9945
Norwegian Wood16.9935
Kafka on the Shore17.9928

OR - Any Condition Can Be True

SQL
-- Books that are cheap OR well-stocked
SELECT title, price, stock_quantity
FROM books
WHERE price < 14 OR stock_quantity > 30;
titlepricestock_quantity
198415.9945
Animal Farm12.9930
Norwegian Wood16.9935

NOT - Negates a Condition

SQL
-- Books NOT in the expensive range
SELECT title, price
FROM books
WHERE NOT (price > 16);
 
-- Equivalent to:
WHERE price <= 16

Operator Precedence

SQL evaluates operators in this order:

  1. NOT
  2. AND
  3. OR

This can lead to unexpected results:

SQL
-- What does this return?
SELECT * FROM books
WHERE author_id = 1 OR author_id = 4 AND price > 16;
 
-- AND binds tighter, so it's interpreted as:
WHERE author_id = 1 OR (author_id = 4 AND price > 16)

Warning

Always use parentheses to make your intent clear. Don't rely on operator precedence—future readers (including yourself) will thank you.

SQL
-- Clear intent with parentheses
SELECT * FROM books
WHERE (author_id = 1 OR author_id = 4) AND price > 16;

Complex Conditions

Combine everything for sophisticated filtering:

SQL
-- Find books that meet these criteria:
-- 1. Published after 1950
-- 2. Either by author 1 or 4
-- 3. Priced between $12 and $18
-- 4. With at least 20 in stock
 
SELECT title, author_id, publication_year, price, stock_quantity
FROM books
WHERE publication_year > 1950
  AND author_id IN (1, 4)
  AND price BETWEEN 12 AND 18
  AND stock_quantity >= 20;

Filtering with Calculated Values

You can filter on expressions, not just columns:

SQL
-- Books with inventory value over $500
SELECT
    title,
    price,
    stock_quantity,
    price * stock_quantity AS inventory_value
FROM books
WHERE price * stock_quantity > 500;

Note

You cannot use column aliases in WHERE because WHERE is processed before SELECT. Repeat the expression or use a subquery/CTE.

SQL
-- This won't work:
WHERE inventory_value > 500  -- ERROR: unknown column
 
-- Either repeat the expression:
WHERE price * stock_quantity > 500
 
-- Or use a subquery (covered later):
SELECT * FROM (
    SELECT *, price * stock_quantity AS inventory_value FROM books
) AS subq
WHERE inventory_value > 500;

Performance Considerations

Some WHERE conditions are more efficient than others:

Indexable Conditions (Fast)

SQL
WHERE customer_id = 5           -- Exact match
WHERE price > 100               -- Range on indexed column
WHERE email LIKE 'john%'        -- Prefix match

Non-Indexable Conditions (Slower)

SQL
WHERE UPPER(email) = 'JOHN@EXAMPLE.COM'  -- Function on column
WHERE email LIKE '%@gmail.com'            -- Suffix/contains match
WHERE price + tax > 100                   -- Expression on column

Performance Tip

For frequently filtered columns, consider adding an index. But avoid using functions on indexed columns in WHERE—this prevents the index from being used.

Practical Examples

SQL
-- Find customers matching a search term
SELECT customer_id, first_name, last_name, email
FROM customers
WHERE first_name LIKE 'A%'
   OR last_name LIKE 'A%'
   OR email LIKE 'a%';

Example 2: Order Analysis

SQL
-- Find high-value completed orders from this month
SELECT order_id, customer_id, total_amount, order_date
FROM orders
WHERE status = 'completed'
  AND total_amount >= 30
  AND order_date >= '2024-01-01'
  AND order_date < '2024-02-01'
ORDER BY total_amount DESC;

Example 3: Inventory Alert

SQL
-- Find books that need reordering
SELECT title, stock_quantity, price
FROM books
WHERE stock_quantity < 25
  AND stock_quantity > 0
ORDER BY stock_quantity ASC;

What's Next

So far we've queried single tables. The next chapter introduces joins—combining data from multiple related tables in a single query.