WHERE is the most-used clause in SQL — period. Every dashboard, every API call, every ML training query begins with "filter to the rows that matter." Get WHERE wrong and your model trains on bad data, your dashboard shows wrong numbers, and your refund email goes to all 4 million users. This lesson trains the muscle that catches the difference.
Learning Objectives
After this lesson, you will be able to:
Use WHERE to filter rows based on conditions like =, !=, <, >, <=, >=
Filter ranges with BETWEEN and match lists with IN
Search text patterns using LIKE with % and _ wildcards (and know when to reach for full-text search instead)
Combine multiple conditions with AND, OR, and NOT — and use parentheses to avoid 2 AM bugs
Sort results with ORDER BY in ascending and descending order, including ties
Combine WHERE, ORDER BY, and LIMIT in a single powerful query
Handle NULL values correctly using IS NULL and IS NOT NULL (the #1 source of silent data bugs)
Your Reflection
Saves automatically
What’s one thing you learned? What’s still confusing?
One missing WHERE wiped GitLab's database in 2017. An engineer ran DELETE FROM projects instead of DELETE FROM projects WHERE .... 300GB of production data, gone. WHERE is not optional, it is a load-bearing safety mechanism
Filtering is where SQL gets powerful. In the last lesson, you got all the data from a table. Now you will learn to ask specific questions and get precise answers. This is like going from "show me the whole library" to "show me only mystery novels published after 2020."
The WHERE clause filters rows based on a condition. Only rows that match the condition are returned:
sql
SELECT * FROM employees
WHERE department = 'Engineering';
This returns only employees in the Engineering department. The database checks every row: "Is this person's department Engineering?" If yes, include them. If no, skip them.
Try it! Run this query and then change 'Engineering' to 'Marketing' or 'Sales' to see different departments.
-- Employees earning more than 90K
SELECT name, salary FROM employees
WHERE salary > 90000;
-- Employees earning exactly 85K
SELECT name, salary FROM employees
WHERE salary = 85000;
-- Employees earning less than 80K
SELECT name, salary FROM employees
WHERE salary < 80000;
Dates are compared as text strings in YYYY-MM-DD format:
sql
-- Employees hired after 2021
SELECT name, hire_date FROM employees
WHERE hire_date > '2021-01-01';
-- Employees hired before 2020
SELECT name, hire_date FROM employees
WHERE hire_date < '2020-01-01';
Try it! Use the playground to find all employees with a salary greater than 85000.
Loading visualization...
What Do You Think?
What does SELECT name FROM employees WHERE salary >= 90000; return?
The answer is names of employees earning 90000 or more. The >= operator means "greater than OR equal to." So an employee earning exactly 90000 would be included.
What if you want employees from Engineering OR Marketing OR Sales? Instead of writing three OR conditions, use IN:
sql
-- Employees in specific departments
SELECT name, department FROM employees
WHERE department IN ('Engineering', 'Marketing');
-- Same result but more verbose:
-- WHERE department = 'Engineering' OR department = 'Marketing'
IN checks if a value matches ANY item in the list. It is cleaner and easier to read than chaining OR conditions, especially with long lists.
sql
-- Find orders for specific products
SELECT * FROM orders
WHERE product IN ('Widget A', 'Widget B', 'Widget C');
-- Find employees with specific IDs
SELECT * FROM employees
WHERE id IN (1, 3, 5, 7, 9);
Sometimes you do not know the exact value -- you want to search by pattern. LIKE lets you use wildcards:
Wildcard
Meaning
Example
%
Any sequence of characters (including none)
'A%' matches "Alice", "Alex", "A"
_
Exactly one character
'_ob' matches "Bob", "Rob"
sql
-- Names starting with 'A'
SELECT name FROM employees
WHERE name LIKE 'A%';
-- Names ending with 'son'
SELECT name FROM employees
WHERE name LIKE '%son';
-- Names containing 'il'
SELECT name FROM employees
WHERE name LIKE '%il%';
-- Names with exactly 3 characters before 'Smith'
SELECT name FROM employees
WHERE name LIKE '___Smith';
Try it! Find all employees whose name starts with a letter between A and D.
-- Engineers earning over 90K
SELECT name, department, salary FROM employees
WHERE department = 'Engineering'
AND salary > 90000;
Both conditions must be true for a row to appear. If someone is in Engineering but earns 85K, they are excluded. If someone earns 95K but is in Marketing, they are also excluded.
When mixing AND and OR, use parentheses to make the logic clear:
sql
-- Engineers earning over 90K, OR anyone in Marketing
SELECT name, department, salary FROM employees
WHERE (department = 'Engineering' AND salary > 90000)
OR department = 'Marketing';
Without parentheses, SQL might interpret the logic differently than you expect. Always use parentheses when combining AND and OR.
Try it! Find employees who are in Engineering AND earn more than 90000.
Sometimes a column has no value. In SQL, this is called NULL. It means "unknown" or "not applicable."
In the employees table, some employees have a manager_id of NULL -- meaning they have no manager (they are top-level).
You cannot use = to check for NULL. This is one of SQL's quirks:
sql
-- WRONG -- this does not work!
SELECT * FROM employees WHERE manager_id = NULL;
-- CORRECT -- use IS NULL
SELECT * FROM employees WHERE manager_id IS NULL;
-- Find employees who DO have a manager
SELECT * FROM employees WHERE manager_id IS NOT NULL;
What Do You Think?
What does WHERE manager_id IS NULL return?
The answer is employees with no manager (manager_id has no value). NULL means the absence of any value -- not zero, not empty string, but truly nothing. IS NULL finds these rows.
By default, SQL does not guarantee any particular order for results. ORDER BY lets you sort:
sql
-- Sort by salary (lowest to highest -- ascending is the default)
SELECT name, salary FROM employees
ORDER BY salary;
-- Sort by salary (highest to lowest -- descending)
SELECT name, salary FROM employees
ORDER BY salary DESC;
-- Sort by department alphabetically, then by salary within each department
SELECT name, department, salary FROM employees
ORDER BY department, salary DESC;
ASC = ascending (A-Z, 0-9, earliest-latest). This is the default -- you can omit it
DESC = descending (Z-A, 9-0, latest-earliest). You must write it explicitly
When you sort by multiple columns, SQL sorts by the first column, then breaks ties with the second:
sql
-- Sort by department (A-Z), then within each department, sort by salary (highest first)
SELECT name, department, salary FROM employees
ORDER BY department ASC, salary DESC;
This is like sorting a spreadsheet: first by one column, then by another to break ties.
Try it! Sort employees by salary from highest to lowest.
WHERE filters rows based on conditions using =, !=, >, <, >=, <= operators
BETWEEN filters ranges inclusively -- BETWEEN 10 AND 20 includes 10 and 20
IN matches a list of values -- cleaner than chaining multiple OR conditions (and the planner often rewrites OR chains into IN anyway)
LIKE searches patterns with % (any characters) and _ (one character) wildcards — for fuzzy/full-text use Postgres' pg_trgm, tsvector, or BigQuery's SEARCH() instead
AND, OR, NOT combine conditions -- always use parentheses when mixing AND and OR
NULL is not a value -- use IS NULL and IS NOT NULL, never = NULL. This bites every SQL engineer at least once
ORDER BY sorts results -- ASC (default) for ascending, DESC for descending. Add a tiebreaker column for deterministic output: ORDER BY salary DESC, id ASC
Clause order is fixed: SELECT, FROM, WHERE, ORDER BY, LIMIT
Quick Check1 / 5
Which query finds employees earning more than 80000 in the Marketing department?
What's next: Filtering and sorting give you the rows you want. Next, you'll learn to summarize them with aggregate functions — COUNT, SUM, AVG, MIN, MAX — and use GROUP BY to compute "per-category" metrics. That's how raw data becomes a dashboard.