IN, BETWEEN, and LIKE Operators
Advanced filtering with IN, BETWEEN, and LIKE operators.
Introduction to Advanced Filtering
You've learned basic WHERE clauses with comparison operators (=, <, >, etc.). Now let's explore three powerful operators that make filtering more convenient and expressive:
- IN - check if a value matches any in a list
- BETWEEN - check if a value falls within a range
- LIKE - check if text matches a pattern
These operators are shortcuts that make your SQL cleaner and more readable than using multiple OR conditions or complex comparisons.
Real-world scenarios:
- "Find orders from these 5 specific customer IDs" → IN
- "Show products priced between 100" → BETWEEN
- "Find all customers whose name starts with 'A'" → LIKE
Operator | Purpose | Example |
|---|---|---|
| IN | Match any value in a list | WHERE city IN ('NYC', 'LA', 'Chicago') |
| BETWEEN | Value within a range (inclusive) | WHERE price BETWEEN 10 AND 100 |
| LIKE | Pattern matching with wildcards | WHERE name LIKE 'A%' |
IN Operator - Matching Multiple Values
IN checks if a value matches any value in a list. It's a cleaner alternative to multiple OR conditions.
An operator that returns TRUE if a value matches any value in a specified list. Equivalent to multiple OR comparisons but more readable.
id | name | department | salary | city |
|---|---|---|---|---|
| 1 | Alice | Engineering | 85000 | NYC |
| 2 | Bob | Marketing | 65000 | LA |
| 3 | Charlie | Sales | 70000 | Chicago |
| 4 | Diana | Engineering | 90000 | NYC |
| 5 | Eve | Sales | 75000 | Boston |
| 6 | Frank | Marketing | 68000 | LA |
Find employees in specific departments:
-- Using IN (clean and readable)
SELECT name, department
FROM employees
WHERE department IN ('Sales', 'Marketing');Without IN (verbose and harder to read):
-- Without IN: multiple OR conditions
SELECT name, department
FROM employees
WHERE department = 'Sales' OR department = 'Marketing';Both queries return the same results, but IN is much cleaner, especially with many values!
IN with numbers:
-- Find specific employees by ID
SELECT name, department
FROM employees
WHERE id IN (1, 3, 5);Pro tip: IN can contain as many values as you need: 3, 10, 100, or more. Most databases handle large IN lists efficiently.
NOT IN - Excluding Values
NOT IN returns rows where the value does NOT match any value in the list.
-- Find employees NOT in NYC or LA
SELECT name, city
FROM employees
WHERE city NOT IN ('NYC', 'LA');Only Charlie (Chicago) and Eve (Boston) are returned. Everyone in NYC or LA is excluded.
NULL gotcha: If the IN list contains NULL, NOT IN can return unexpected results. NOT IN (1, 2, NULL) will never match any row because NULL comparisons are always unknown.
Safe approach: Filter out NULLs explicitly:
WHERE city NOT IN ('NYC', 'LA')
AND city IS NOT NULL
IN with Subqueries
IN becomes even more powerful when combined with subqueries:
order_id | customer_id | product_id | total |
|---|---|---|---|
| 1 | 101 | 1 | 150.00 |
| 2 | 102 | 2 | 200.00 |
| 3 | 101 | 3 | 180.00 |
| 4 | 103 | 1 | 150.00 |
customer_id | name | email |
|---|---|---|
| 101 | Alice | alice@example.com |
| 102 | Bob | bob@example.com |
| 103 | Charlie | charlie@example.com |
| 104 | Diana | diana@example.com |
-- Find customers who have placed orders
SELECT name, email
FROM customers
WHERE customer_id IN (
SELECT DISTINCT customer_id FROM orders
);The subquery returns customer IDs (101, 102, 103), then IN checks which customers match.
Find customers who have NOT placed orders:
SELECT name, email
FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders
);Diana (104) has no orders, so she's the only one returned.
BETWEEN Operator - Range Filtering
BETWEEN checks if a value falls within a range (inclusive of both endpoints).
An operator that checks if a value is within a specified range (inclusive). value BETWEEN low AND high is equivalent to value >= low AND value <= high.
product_id | product_name | price | stock |
|---|---|---|---|
| 1 | Laptop | 999.99 | 45 |
| 2 | Mouse | 29.99 | 120 |
| 3 | Desk Chair | 199.99 | 30 |
| 4 | Monitor | 349.99 | 60 |
| 5 | Keyboard | 79.99 | 95 |
| 6 | Desk Lamp | 39.99 | 85 |
Find products in a price range:
-- Products priced between $50 and $500 (inclusive)
SELECT product_name, price
FROM products
WHERE price BETWEEN 50 AND 500;Without BETWEEN (more verbose):
-- Equivalent without BETWEEN
SELECT product_name, price
FROM products
WHERE price >= 50 AND price <= 500;Same results, but BETWEEN is cleaner!
Important: BETWEEN is inclusive - it includes both 50 and 500. If you need exclusive ranges, use < and >:
-- Exclusive range: price > 50 AND < 500
SELECT product_name, price
FROM products
WHERE price > 50 AND price < 500;BETWEEN with Dates
BETWEEN works great with dates for time-based filtering:
order_id | customer_id | order_date | total |
|---|---|---|---|
| 1 | 101 | 2024-01-15 | 150.00 |
| 2 | 102 | 2024-01-20 | 200.00 |
| 3 | 101 | 2024-02-01 | 180.00 |
| 4 | 103 | 2024-02-10 | 150.00 |
| 5 | 102 | 2024-03-05 | 220.00 |
-- Orders placed in February 2024
SELECT order_id, order_date, total
FROM orders_with_dates
WHERE order_date BETWEEN '2024-02-01' AND '2024-02-29';Find orders from the last 30 days:
-- Last 30 days (assuming today is 2024-03-15)
SELECT order_id, order_date, total
FROM orders_with_dates
WHERE order_date BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW();
-- Or in PostgreSQL:
WHERE order_date BETWEEN NOW() - INTERVAL '30 days' AND NOW();Date ranges caution: BETWEEN with dates includes the entire end date. BETWEEN '2024-02-01' AND '2024-02-29' includes all of February 29th (00:00:00 to 23:59:59).
NOT BETWEEN - Excluding Ranges
NOT BETWEEN returns values outside the specified range:
-- Products NOT in the $50-$500 range (cheap or expensive)
SELECT product_name, price
FROM products
WHERE price NOT BETWEEN 50 AND 500;Returns products priced under 500.
LIKE Operator - Pattern Matching
LIKE performs pattern matching on text using wildcards.
A pattern matching operator for text. Uses wildcards: % (matches any sequence of characters) and _ (matches exactly one character).
Wildcard | Meaning | Example | Matches |
|---|---|---|---|
| % | Zero or more characters | 'A%' | Alice, Apple, A, AB |
| _ | Exactly one character | 'A_' | AB, A1, AX |
customer_id | name | email | phone |
|---|---|---|---|
| 101 | Alice Smith | alice@gmail.com | 555-1234 |
| 102 | Bob Johnson | bob@yahoo.com | 555-5678 |
| 103 | Charlie Brown | charlie@gmail.com | 555-9012 |
| 104 | Diana Prince | diana@hotmail.com | 555-3456 |
| 105 | Alice Jones | ajones@gmail.com | 555-7890 |
Find customers whose name starts with 'A':
SELECT name, email
FROM customers_full
WHERE name LIKE 'A%';'A%' matches any string starting with 'A' followed by anything (or nothing).
Find customers whose name ends with 'son':
SELECT name, email
FROM customers_full
WHERE name LIKE '%son';'%son' matches any string ending with 'son'.
Find customers with 'a' anywhere in their name:
SELECT name, email
FROM customers_full
WHERE name LIKE '%a%';'%a%' matches any string containing 'a' anywhere.
Case sensitivity: LIKE is case-insensitive in MySQL and SQL Server, but case-sensitive in PostgreSQL and SQLite. Use ILIKE in PostgreSQL for case-insensitive matching.
LIKE with Underscore Wildcard
The underscore (_) matches exactly one character:
-- Find names with exactly 3 letters
SELECT name
FROM customers_full
WHERE name LIKE '___';If Bob was the only 3-letter name, he'd be the only match. Three underscores = exactly 3 characters.
Phone numbers in specific format:
-- Phone numbers starting with 555-12 (last two digits vary)
SELECT name, phone
FROM customers_full
WHERE phone LIKE '555-12__';'555-12__' matches 555-12XX where XX can be any two digits.
Common LIKE Patterns
Pattern 1: Emails from a specific domain
-- Gmail users
SELECT name, email
FROM customers_full
WHERE email LIKE '%@gmail.com';Pattern 2: Names starting with specific letters
-- Names starting with A, B, or C
SELECT name
FROM customers_full
WHERE name LIKE 'A%'
OR name LIKE 'B%'
OR name LIKE 'C%';Pattern 3: Finding specific word patterns
-- Products containing "Desk"
SELECT product_name
FROM products
WHERE product_name LIKE '%Desk%';NOT LIKE - Excluding Patterns
NOT LIKE returns rows that do NOT match the pattern:
-- Customers NOT using Gmail
SELECT name, email
FROM customers_full
WHERE email NOT LIKE '%@gmail.com';Bob (Yahoo) and Diana (Hotmail) don't use Gmail.
Exclude names starting with 'A':
SELECT name
FROM customers_full
WHERE name NOT LIKE 'A%';Escaping Special Characters
What if you need to search for an actual % or _ character?
promo_code | description |
|---|---|
| SAVE10 | 10% off |
| SAVE_20 | 20% off |
| SPRING_SALE | 15% off |
| SUMMER25 | 25% off |
-- Find promo codes with underscore in the name
SELECT promo_code, description
FROM discounts
WHERE promo_code LIKE '%\_%' ESCAPE '\';ESCAPE '\' tells SQL that \ is the escape character. \_ means "literal underscore, not wildcard".
Find descriptions containing literal '%':
-- Find text containing % character
SELECT promo_code, description
FROM discounts
WHERE description LIKE '%\%%' ESCAPE '\';All descriptions contain '%', so all are returned.
Combining IN, BETWEEN, and LIKE
These operators work great together:
-- Products priced $50-$500, in specific categories, with "e" in name
SELECT product_name, price
FROM products
WHERE price BETWEEN 50 AND 500
AND category IN ('Electronics', 'Furniture')
AND product_name LIKE '%e%';Complex customer search:
-- High-value customers in specific cities, Gmail users
SELECT name, email, city
FROM customers_full
WHERE customer_id IN (
SELECT customer_id FROM orders
WHERE total BETWEEN 150 AND 250
)
AND city IN ('NYC', 'LA', 'Chicago')
AND email LIKE '%@gmail.com';Performance Considerations
IN operator:
- Generally fast, especially with indexes
- Large IN lists (1000+ values) can be slow
- Consider temporary tables for very large lists
BETWEEN operator:
- Very efficient with indexes on the column
- Database can use index range scans
- Much faster than separate >= and <= checks
LIKE operator:
- Fast:
LIKE 'ABC%'(starts with) - can use index - Slow:
LIKE '%ABC'orLIKE '%ABC%'(contains) - full table scan - Leading wildcard (%) prevents index usage
Optimization tips:
- Use indexes on columns in WHERE with IN, BETWEEN, LIKE
- Avoid leading wildcards in LIKE when possible (
'ABC%'is fast,'%ABC'is slow) - Use full-text search for complex text searches instead of LIKE '%word%'
- Consider BETWEEN instead of >= AND <= for cleaner, potentially faster queries
Common Mistakes to Avoid
Don't make these mistakes:
-
Confusing BETWEEN's inclusivity: ✅ Remember it's inclusive
BETWEEN 1 AND 10includes both 1 and 10- For exclusive, use
> 1 AND < 10
-
NOT IN with NULL values: ❌ Dangerous!
WHERE col NOT IN (1, 2, NULL)may return no rows- Always filter NULLs:
WHERE col NOT IN (...) OR col IS NULL
-
LIKE without wildcards: ❌ Pointless
LIKE 'Alice'is the same as= 'Alice'- Use = for exact matches (it's faster)
-
Wrong BETWEEN order: ❌
- Wrong:
BETWEEN 100 AND 10(no results!) - Right:
BETWEEN 10 AND 100(low to high)
- Wrong:
-
Case sensitivity surprises: ⚠️
- LIKE is case-sensitive in PostgreSQL, not in MySQL
- Test your database's behavior or use UPPER/LOWER
-
Overusing LIKE with leading %: ❌ Very slow
LIKE '%word%'scans entire table- Consider full-text search for better performance
-
Forgetting to escape special characters: ❌
- Searching for literal '_' or '%' requires escaping
- Use ESCAPE clause:
LIKE '%\_%' ESCAPE '\'
-
Using IN with single value: Unnecessary
WHERE id IN (5)should beWHERE id = 5- IN is for multiple values
Practical Examples
Example 1: Product search with multiple filters
-- Mid-priced electronics with specific keywords
SELECT product_name, price, category
FROM products
WHERE category IN ('Electronics', 'Computers')
AND price BETWEEN 50 AND 300
AND (product_name LIKE '%key%' OR product_name LIKE '%mouse%')
ORDER BY price;Example 2: Customer segmentation
-- VIP customers in major cities with Gmail
SELECT name, email, city
FROM customers_full
WHERE customer_id IN (
SELECT customer_id FROM orders
GROUP BY customer_id
HAVING SUM(total) BETWEEN 500 AND 2000
)
AND city IN ('NYC', 'LA', 'Chicago', 'Boston')
AND email LIKE '%@gmail.com'
ORDER BY name;Example 3: Date-based order analysis
-- Q1 2024 orders from specific customers
SELECT order_id, order_date, total
FROM orders_with_dates
WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31'
AND customer_id IN (101, 102, 105)
AND total BETWEEN 100 AND 300
ORDER BY order_date DESC;Key Takeaways
What you learned:
✅ IN checks if a value matches any in a list (cleaner than multiple ORs) ✅ NOT IN excludes values in the list (watch out for NULLs!) ✅ BETWEEN checks if value is in a range (inclusive of endpoints) ✅ NOT BETWEEN excludes values in the range ✅ LIKE performs pattern matching with wildcards (% and ) ✅ NOT LIKE excludes matching patterns ✅ % matches zero or more characters ✅ **** matches exactly one character ✅ IN works great with subqueries ✅ BETWEEN works with numbers, dates, and text ✅ Leading wildcards in LIKE (%) prevent index usage ✅ Use ESCAPE clause for literal % or _ characters
Performance tips:
- IN and BETWEEN are index-friendly
- LIKE with leading % requires full table scan
- Avoid NOT IN with columns containing NULLs
- Use = instead of LIKE when no wildcards needed
Coming up next: You'll explore window functions and PARTITION BY to perform calculations across groups of rows without collapsing them like GROUP BY does!
Practice Exercise: Try these queries:
- Find products with names starting with 'D' or 'K'
- List orders between January 1 and February 28, 2024
- Find customers in 'NYC', 'LA', or 'Boston' with Yahoo emails
- Show products priced 500 NOT in the Electronics category
- Find phone numbers starting with '555-1' (any last 3 digits)