IN and BETWEEN Operators
Check if a value matches any value in a list with IN, or falls within a range with BETWEEN.
- IN Operator β Match against a list of values
- BETWEEN Operator β Match within an inclusive range
- NOT IN / NOT BETWEEN β Negate both operators Simplify complex OR conditions and range checks.
What Are IN and BETWEEN?
DfSet and Range Operators
IN checks whether a value matches any value in a specified list. BETWEEN checks whether a value falls within an inclusive range (both endpoints included). Both simplify WHERE clause conditions and improve readability.
-- IN: match against a list
SELECT * FROM employees WHERE department IN ('Engineering', 'Sales');
-- BETWEEN: match within a range
SELECT * FROM employees WHERE salary BETWEEN 60000 AND 80000;
BETWEEN 60000 AND 80000 includes salaries of exactly 60000 and exactly 80000.IN Operator
IN replaces multiple OR conditions on the same column with a cleaner syntax.
DfIN Operator
column IN (value1, value2, ...)
Returns TRUE if the column value matches any value in the list. Works with numbers, strings, and dates.
-- IN with numeric values
SELECT * FROM products
WHERE category_id IN (1, 2, 3, 4, 5);
-- IN with string values
SELECT * FROM customers
WHERE country IN ('USA', 'Canada', 'Mexico');
-- IN with date values
SELECT * FROM orders
WHERE order_date IN ('2024-01-01', '2024-06-15', '2024-12-31');
-- IN is equivalent to multiple OR conditions
-- These two queries return the same result:
SELECT * FROM employees WHERE department IN ('Engineering', 'Sales', 'Marketing');
SELECT * FROM employees
WHERE department = 'Engineering'
OR department = 'Sales'
OR department = 'Marketing';
| Operator | Syntax | Example |
|---|---|---|
| IN | column IN (list) | WHERE id IN (1, 2, 3) |
| Multiple OR | col = v1 OR col = v2 | WHERE id = 1 OR id = 2 OR id = 3 |
IN with Subqueries
IN becomes powerful when the list is generated dynamically from another table.
DfIN with Subquery
column IN (SELECT expression FROM table WHERE condition)
The subquery returns a list of values. The outer query includes rows where the column matches any value in that list.
-- Find customers who have placed orders
SELECT first_name, last_name
FROM customers
WHERE id IN (SELECT DISTINCT customer_id FROM orders);
-- Find products in active categories
SELECT * FROM products
WHERE category_id IN (
SELECT category_id
FROM categories
WHERE status = 'Active'
);
-- Find employees in high-budget departments
SELECT * FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE budget > 1000000
);
NOT IN
NOT IN returns rows where the column value does not match any value in the list.
-- Find employees NOT in specific departments
SELECT * FROM employees
WHERE department NOT IN ('Engineering', 'Sales');
-- Find customers who have never placed orders
SELECT * FROM customers
WHERE id NOT IN (SELECT DISTINCT customer_id FROM orders);
-- Safer alternative: NOT EXISTS
SELECT * FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);
-- Also safe: LEFT JOIN
SELECT c.* FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.customer_id IS NULL;
BETWEEN Operator
BETWEEN checks whether a value falls within an inclusive range. It works with numbers, strings, and dates.
DfBETWEEN Operator
column BETWEEN start AND end
Equivalent to column >= start AND column <= end. Both endpoints are included.
-- Numeric range
SELECT * FROM products
WHERE price BETWEEN 50 AND 100;
-- String range (alphabetical ordering)
SELECT * FROM customers
WHERE last_name BETWEEN 'A' AND 'M';
-- Date range
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31';
-- NOT BETWEEN
SELECT * FROM employees
WHERE salary NOT BETWEEN 50000 AND 80000;
| Expression | Equivalent To |
|---|---|
x BETWEEN a AND b | x >= a AND x <= b |
x NOT BETWEEN a AND b | x < a OR x > b |
BETWEEN with DateTime
DateTime ranges require careful handling because BETWEEN is inclusive on both ends.
DfDateTime BETWEEN
When using BETWEEN with datetime columns, the end value must include the time component to capture all rows on the end date. Otherwise, rows with times after midnight are excluded.
-- Incorrect: misses orders on Jan 31 after midnight
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
-- Correct: includes all of Jan 31
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01 00:00:00' AND '2024-01-31 23:59:59';
-- Current year orders using functions
SELECT * FROM orders
WHERE order_date BETWEEN
DATE_FORMAT(CURRENT_DATE, '%Y-01-01')
AND DATE_FORMAT(CURRENT_DATE, '%Y-12-31');
column >= start AND column < end_plus_one to avoid time component issues entirely.Performance Considerations
DfIN and BETWEEN Performance
Both operators can use indexes effectively. IN with a short list is optimized to range scans. BETWEEN always uses range scans. For very large IN lists, consider temporary tables or JOINs.
-- IN can use indexes
SELECT * FROM employees
WHERE department IN ('Engineering', 'Sales', 'Marketing');
-- BETWEEN uses range scan
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
-- For very large IN lists, consider a JOIN
SELECT e.* FROM employees e
INNER JOIN (
SELECT 'Engineering' AS dept
UNION ALL SELECT 'Sales'
UNION ALL SELECT 'Marketing'
) d ON e.department = d.dept;
| Operator | Index Usage | Best For |
|---|---|---|
| IN (small list) | Range scan | Few discrete values |
| IN (large list) | May degrade | Consider JOIN |
| BETWEEN | Range scan | Continuous ranges |
| NOT IN | Sometimes | Prefer NOT EXISTS |
| NOT BETWEEN | Range scan | Exclusion ranges |
Practice Exercises
Exercise 1: Find all Electronics or Clothing products priced between 100.
-- Solution
SELECT * FROM products
WHERE category IN ('Electronics', 'Clothing')
AND price BETWEEN 20 AND 100;
Exercise 2: Find customers from major cities who registered in 2024.
-- Solution
SELECT * FROM customers
WHERE city IN ('New York', 'Los Angeles', 'Chicago')
AND registration_date BETWEEN '2024-01-01' AND '2024-12-31';
Exercise 3: Find orders that are NOT in pending or cancelled status.
-- Solution
SELECT * FROM orders
WHERE status NOT IN ('Pending', 'Cancelled');
Summary
Key Takeaways
- IN checks if a value matches any value in a list
- BETWEEN checks if a value falls within an inclusive range
- Both work with numbers, strings, and dates
- NOT IN and NOT BETWEEN negate the conditions
- NOT IN with NULLs returns no rows β use NOT EXISTS instead
- BETWEEN is inclusive on both endpoints
- DateTime BETWEEN requires careful handling of the time component
- Both operators can leverage indexes for good performance
| Operator | Use Case | Negation |
|---|---|---|
| IN | Match a list of values | NOT IN |
| BETWEEN | Match an inclusive range | NOT BETWEEN |
| EXISTS | Match subquery results | NOT EXISTS |
| LIKE | Pattern matching | NOT LIKE |