Filtering Data
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:
SELECT title, price, stock_quantity
FROM books
WHERE price > 15;| title | price | stock_quantity |
|---|---|---|
| 1984 | 15.99 | 45 |
| One Hundred Years of Solitude | 18.99 | 20 |
| Norwegian Wood | 16.99 | 35 |
| Kafka on the Shore | 17.99 | 28 |
The database evaluates the condition for each row and includes only rows where the condition is true.
Comparison Operators
Basic Comparisons
| Operator | Meaning | Example |
|---|---|---|
= | Equal to | price = 15.99 |
<> or != | Not equal to | status <> 'cancelled' |
< | Less than | stock_quantity < 10 |
> | Greater than | price > 20 |
<= | Less than or equal to | publication_year <= 2000 |
>= | Greater than or equal to | price >= 15 |
-- 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:
-- Authors whose last name comes before 'M' alphabetically
SELECT first_name, last_name
FROM authors
WHERE last_name < 'M';| first_name | last_name |
|---|---|
| Jane | Austen |
| Gabriel | Garcí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:
-- 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):
-- Books priced between $14 and $17
SELECT title, price
FROM books
WHERE price BETWEEN 14 AND 17;| title | price |
|---|---|
| Pride and Prejudice | 14.99 |
| 1984 | 15.99 |
| Norwegian Wood | 16.99 |
BETWEEN is inclusive on both ends. It's equivalent to:
WHERE price >= 14 AND price <= 17Warning
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
-- 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:
-- Books by authors 1 or 4
SELECT title, author_id
FROM books
WHERE author_id IN (1, 4);| title | author_id |
|---|---|
| 1984 | 1 |
| Animal Farm | 1 |
| Norwegian Wood | 4 |
| Kafka on the Shore | 4 |
This is equivalent to multiple OR conditions:
WHERE author_id = 1 OR author_id = 4NOT IN
-- 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:
| Wildcard | Meaning | Example |
|---|---|---|
% | Any sequence of characters | '%wood' |
_ | Any single character | '_984' |
-- Books with 'Wood' in the title
SELECT title FROM books WHERE title LIKE '%Wood%';| title |
|---|
| Norwegian Wood |
-- Books starting with 'A'
SELECT title FROM books WHERE title LIKE 'A%';| title |
|---|
| Animal Farm |
-- Email addresses from email.com domain
SELECT email FROM customers WHERE email LIKE '%@email.com';Common Patterns
-- 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)
-- PostgreSQL case-insensitive search
SELECT title FROM books WHERE title ILIKE '%the%';Escaping Wildcards
To match literal % or _ characters:
-- 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).
-- 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_id | name | birth_date |
|---|---|---|
| 1 | Alice | 1980-05-15 |
| 2 | Bob | NULL |
-- 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
-- Expensive books with good stock
SELECT title, price, stock_quantity
FROM books
WHERE price > 15 AND stock_quantity > 25;| title | price | stock_quantity |
|---|---|---|
| 1984 | 15.99 | 45 |
| Norwegian Wood | 16.99 | 35 |
| Kafka on the Shore | 17.99 | 28 |
OR - Any Condition Can Be True
-- Books that are cheap OR well-stocked
SELECT title, price, stock_quantity
FROM books
WHERE price < 14 OR stock_quantity > 30;| title | price | stock_quantity |
|---|---|---|
| 1984 | 15.99 | 45 |
| Animal Farm | 12.99 | 30 |
| Norwegian Wood | 16.99 | 35 |
NOT - Negates a Condition
-- Books NOT in the expensive range
SELECT title, price
FROM books
WHERE NOT (price > 16);
-- Equivalent to:
WHERE price <= 16Operator Precedence
SQL evaluates operators in this order:
- NOT
- AND
- OR
This can lead to unexpected results:
-- 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.
-- 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:
-- 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:
-- 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.
-- 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)
WHERE customer_id = 5 -- Exact match
WHERE price > 100 -- Range on indexed column
WHERE email LIKE 'john%' -- Prefix matchNon-Indexable Conditions (Slower)
WHERE UPPER(email) = 'JOHN@EXAMPLE.COM' -- Function on column
WHERE email LIKE '%@gmail.com' -- Suffix/contains match
WHERE price + tax > 100 -- Expression on columnPerformance 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
Example 1: Customer Search
-- 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
-- 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
-- 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.