SQL Tutorial

SQL WHERE Clause: Complete Guide to Filtering Data (2026)

Master the SQL WHERE clause from basic comparisons to advanced subqueries. This guide covers every operator, NULL handling, performance tips, and real-world patterns you will use daily.

Mar 11, 2026 18 min read

What is the SQL WHERE Clause?

The WHERE clause filters which rows a SQL statement affects. It evaluates a condition for every row in the table, and only rows where the condition is true are included in the result. Without WHERE, a SELECT returns every row, an UPDATE changes every row, and a DELETE removes every row.

WHERE runs early in the SQL execution order. The database processes clauses in this sequence:

  1. FROM and JOIN - identify the tables and combine rows
  2. WHERE - filter individual rows
  3. GROUP BY - group the surviving rows
  4. HAVING - filter groups
  5. SELECT - choose which columns to return
  6. ORDER BY - sort the result
  7. LIMIT - cap the number of rows

This means WHERE cannot reference column aliases defined in SELECT, and it cannot use aggregate functions like COUNT or SUM (use HAVING for those). WHERE filters rows before grouping happens.

Here is the sample data we will use throughout this guide:

CREATE TABLE employees (
    id         INT PRIMARY KEY,
    name       VARCHAR(50),
    department VARCHAR(30),
    salary     DECIMAL(10,2),
    hire_date  DATE,
    email      VARCHAR(100),
    manager_id INT
);

INSERT INTO employees VALUES
(1, 'Alice Chen',    'Engineering', 95000,  '2023-03-15', 'alice@company.com',    NULL),
(2, 'Bob Martinez',  'Marketing',  72000,  '2024-01-10', 'bob@company.com',      1),
(3, 'Charlie Kim',   'Engineering', 88000,  '2023-08-22', 'charlie@company.com',  1),
(4, 'Diana Patel',   'Sales',      67000,  '2025-06-01', 'diana@company.com',    2),
(5, 'Eve Johnson',   'Engineering', 105000, '2022-11-05', 'eve@company.com',      1),
(6, 'Frank Lee',     'Marketing',  61000,  '2025-09-14', NULL,                   2),
(7, 'Grace Wu',      'Sales',      78000,  '2024-04-30', 'grace@company.com',    4),
(8, 'Henry Davis',   'Engineering', 92000,  '2023-12-01', 'henry@company.com',   5);

Basic WHERE Syntax

The simplest WHERE clause uses a single condition with the equals operator:

SELECT name, department, salary
FROM employees
WHERE department = 'Engineering';

Result

 name         | department  | salary
--------------+-------------+---------
 Alice Chen   | Engineering | 95000
 Charlie Kim  | Engineering | 88000
 Eve Johnson  | Engineering | 105000
 Henry Davis  | Engineering | 92000

The database scans every row in the employees table, checks whether the department column equals 'Engineering', and returns only the matching rows.

WHERE works with UPDATE and DELETE too:

-- Give Engineering a 10% raise
UPDATE employees
SET salary = salary * 1.10
WHERE department = 'Engineering';

-- Remove employees from a dissolved department
DELETE FROM employees
WHERE department = 'Legacy';

Important: Always include a WHERE clause with UPDATE and DELETE unless you intentionally want to affect every row. Running DELETE FROM employees without WHERE deletes the entire table.

Comparison Operators

SQL provides six comparison operators for WHERE conditions. Each evaluates to true, false, or NULL (when one side is NULL).

Equal to (=)

SELECT name, salary FROM employees
WHERE salary = 95000;
 name       | salary
------------+--------
 Alice Chen | 95000

Not equal to (<> or !=)

SELECT name, department FROM employees
WHERE department <> 'Engineering';
 name          | department
---------------+------------
 Bob Martinez  | Marketing
 Diana Patel   | Sales
 Frank Lee     | Marketing
 Grace Wu      | Sales

Both <> and != work in all major databases. The SQL standard uses <>, so it is the safer choice for portability.

Less than (<) and less than or equal (<=)

SELECT name, salary FROM employees
WHERE salary < 70000;
 name        | salary
-------------+--------
 Diana Patel | 67000
 Frank Lee   | 61000
SELECT name, salary FROM employees
WHERE salary <= 72000;
 name          | salary
---------------+--------
 Bob Martinez  | 72000
 Diana Patel   | 67000
 Frank Lee     | 61000

Greater than (>) and greater than or equal (>=)

SELECT name, salary FROM employees
WHERE salary > 90000;
 name        | salary
-------------+---------
 Alice Chen  | 95000
 Eve Johnson | 105000
 Henry Davis | 92000
SELECT name, salary FROM employees
WHERE salary >= 92000;
 name        | salary
-------------+---------
 Alice Chen  | 95000
 Eve Johnson | 105000
 Henry Davis | 92000

Comparison operators work with numbers, strings (alphabetical order), and dates. Dates compare chronologically:

SELECT name, hire_date FROM employees
WHERE hire_date >= '2025-01-01';
 name        | hire_date
-------------+------------
 Diana Patel | 2025-06-01
 Frank Lee   | 2025-09-14

Text Filtering with LIKE

The LIKE operator matches text patterns using two wildcard characters:

  • % matches any sequence of zero or more characters
  • _ matches exactly one character

Starts with

SELECT name FROM employees
WHERE name LIKE 'A%';
 name
-----------
 Alice Chen

Ends with

SELECT name, email FROM employees
WHERE email LIKE '%@company.com';
 name          | email
---------------+---------------------
 Alice Chen    | alice@company.com
 Bob Martinez  | bob@company.com
 Charlie Kim   | charlie@company.com
 Diana Patel   | diana@company.com
 Eve Johnson   | eve@company.com
 Grace Wu      | grace@company.com
 Henry Davis   | henry@company.com

Contains

SELECT name FROM employees
WHERE name LIKE '%son%';
 name
-----------
 Eve Johnson

Single character wildcard

-- Find 3-letter first names (first name + space + last name)
SELECT name FROM employees
WHERE name LIKE '___ %';
 name
---------------
 Bob Martinez
 Eve Johnson

Case sensitivity

LIKE is case-sensitive in PostgreSQL and case-insensitive in MySQL (with default collation) and SQL Server. If you need case-insensitive matching in PostgreSQL, use ILIKE:

-- PostgreSQL only
SELECT name FROM employees
WHERE name ILIKE 'alice%';

-- Works everywhere: use LOWER() or UPPER()
SELECT name FROM employees
WHERE LOWER(name) LIKE 'alice%';

Performance note: Using LOWER() or UPPER() on the column prevents index usage. For frequent case-insensitive searches, create a functional index: CREATE INDEX idx_name_lower ON employees(LOWER(name)).

NOT LIKE

SELECT name, department FROM employees
WHERE department NOT LIKE 'Eng%';
 name          | department
---------------+------------
 Bob Martinez  | Marketing
 Diana Patel   | Sales
 Frank Lee     | Marketing
 Grace Wu      | Sales

Describe your text filter in plain English and let AI2SQL generate the correct LIKE pattern for you.

Filtering with IN and NOT IN

The IN operator checks whether a value matches any value in a list. It is cleaner than writing multiple OR conditions.

-- Instead of this
SELECT name, department FROM employees
WHERE department = 'Engineering'
   OR department = 'Sales';

-- Write this
SELECT name, department FROM employees
WHERE department IN ('Engineering', 'Sales');

Result

 name        | department
-------------+-------------
 Alice Chen  | Engineering
 Charlie Kim | Engineering
 Diana Patel | Sales
 Eve Johnson | Engineering
 Grace Wu    | Sales
 Henry Davis | Engineering

NOT IN

SELECT name, department FROM employees
WHERE department NOT IN ('Engineering', 'Marketing');
 name        | department
-------------+------------
 Diana Patel | Sales
 Grace Wu    | Sales

IN with a subquery

IN becomes powerful when combined with a subquery. Find employees whose department has more than two people:

SELECT name, department FROM employees
WHERE department IN (
    SELECT department
    FROM employees
    GROUP BY department
    HAVING COUNT(*) > 2
);
 name        | department
-------------+-------------
 Alice Chen  | Engineering
 Charlie Kim | Engineering
 Eve Johnson | Engineering
 Henry Davis | Engineering

NULL warning: If the subquery or list in NOT IN contains a NULL value, the entire NOT IN returns no rows. This is a common source of bugs. Use NOT EXISTS instead when NULLs are possible.

-- This returns NOTHING if any manager_id is NULL
SELECT name FROM employees
WHERE id NOT IN (SELECT manager_id FROM employees);

-- Safe alternative using NOT EXISTS
SELECT e.name FROM employees e
WHERE NOT EXISTS (
    SELECT 1 FROM employees m WHERE m.manager_id = e.id
);

Range Filtering with BETWEEN

BETWEEN checks whether a value falls within an inclusive range. It is equivalent to >= low AND <= high but more readable.

Numeric range

SELECT name, salary FROM employees
WHERE salary BETWEEN 70000 AND 95000;
 name          | salary
---------------+--------
 Alice Chen    | 95000
 Bob Martinez  | 72000
 Charlie Kim   | 88000
 Grace Wu      | 78000
 Henry Davis   | 92000

BETWEEN is inclusive on both ends. The query above is identical to WHERE salary >= 70000 AND salary <= 95000.

Date range

SELECT name, hire_date FROM employees
WHERE hire_date BETWEEN '2024-01-01' AND '2024-12-31';
 name          | hire_date
---------------+------------
 Bob Martinez  | 2024-01-10
 Grace Wu      | 2024-04-30

Date trap: When filtering datetime columns (not just date), BETWEEN '2024-01-01' AND '2024-12-31' misses any time after midnight on Dec 31. Use WHERE hire_date >= '2024-01-01' AND hire_date < '2025-01-01' for datetime columns to capture the entire day.

NOT BETWEEN

SELECT name, salary FROM employees
WHERE salary NOT BETWEEN 70000 AND 95000;
 name        | salary
-------------+---------
 Diana Patel | 67000
 Eve Johnson | 105000
 Frank Lee   | 61000

Combining Conditions: AND, OR, NOT

Real queries almost always combine multiple conditions. SQL provides three logical operators: AND, OR, and NOT.

AND - both conditions must be true

SELECT name, department, salary FROM employees
WHERE department = 'Engineering'
  AND salary > 90000;
 name        | department  | salary
-------------+-------------+---------
 Alice Chen  | Engineering | 95000
 Eve Johnson | Engineering | 105000
 Henry Davis | Engineering | 92000

OR - at least one condition must be true

SELECT name, department, salary FROM employees
WHERE department = 'Sales'
   OR salary > 100000;
 name        | department  | salary
-------------+-------------+---------
 Diana Patel | Sales       | 67000
 Eve Johnson | Engineering | 105000
 Grace Wu    | Sales       | 78000

NOT - inverts a condition

SELECT name, department FROM employees
WHERE NOT department = 'Engineering';
 name          | department
---------------+------------
 Bob Martinez  | Marketing
 Diana Patel   | Sales
 Frank Lee     | Marketing
 Grace Wu      | Sales

Operator precedence matters

AND has higher precedence than OR. This is the single most common source of WHERE clause bugs. Consider this query:

-- What does this return?
SELECT name, department, salary FROM employees
WHERE department = 'Marketing'
   OR department = 'Sales'
  AND salary > 70000;

You might expect it to find Marketing or Sales employees earning over $70,000. But AND binds tighter than OR, so the database reads it as:

-- This is what actually executes
WHERE department = 'Marketing'
   OR (department = 'Sales' AND salary > 70000);
 name          | department | salary
---------------+------------+--------
 Bob Martinez  | Marketing  | 72000
 Frank Lee     | Marketing  | 61000
 Grace Wu      | Sales      | 78000

All Marketing employees are returned regardless of salary. Only Sales employees are filtered by salary. Fix this with parentheses:

-- Correct: parentheses override precedence
SELECT name, department, salary FROM employees
WHERE (department = 'Marketing' OR department = 'Sales')
  AND salary > 70000;
 name          | department | salary
---------------+------------+--------
 Bob Martinez  | Marketing  | 72000
 Grace Wu      | Sales      | 78000

Rule of thumb: Always use parentheses when mixing AND and OR. It makes the intent clear and prevents precedence bugs.

Handling NULL Values

NULL in SQL means "unknown" or "missing." It is not zero, not an empty string, and not false. NULL has special behavior that catches many developers off guard.

Why = NULL does not work

Any comparison with NULL using standard operators returns NULL (not true, not false). Since WHERE only includes rows where the condition is true, rows with NULL are always excluded:

-- This returns NOTHING, even though Frank has NULL email
SELECT name, email FROM employees
WHERE email = NULL;

-- This also returns nothing
SELECT name FROM employees
WHERE NULL = NULL;

IS NULL and IS NOT NULL

-- Find employees with no email
SELECT name, email FROM employees
WHERE email IS NULL;
 name      | email
-----------+-------
 Frank Lee | NULL
-- Find employees who have an email
SELECT name, email FROM employees
WHERE email IS NOT NULL;
 name          | email
---------------+---------------------
 Alice Chen    | alice@company.com
 Bob Martinez  | bob@company.com
 Charlie Kim   | charlie@company.com
 Diana Patel   | diana@company.com
 Eve Johnson   | eve@company.com
 Grace Wu      | grace@company.com
 Henry Davis   | henry@company.com

COALESCE - providing a default for NULL

COALESCE returns the first non-NULL value from its arguments. Use it to replace NULLs with a default:

SELECT
    name,
    COALESCE(email, 'no-email@placeholder.com') AS contact_email
FROM employees
WHERE department = 'Marketing';
 name          | contact_email
---------------+---------------------------
 Bob Martinez  | bob@company.com
 Frank Lee     | no-email@placeholder.com

NULL in NOT IN (the hidden trap)

This is worth repeating because it causes so many bugs. If any value in a NOT IN list is NULL, the entire expression returns no rows:

-- manager_id values: NULL, 1, 1, 2, 1, 2, 4, 5
-- This returns ZERO rows because NULL is in the list
SELECT name FROM employees
WHERE id NOT IN (SELECT manager_id FROM employees);

-- Fix: exclude NULLs from the subquery
SELECT name FROM employees
WHERE id NOT IN (
    SELECT manager_id FROM employees
    WHERE manager_id IS NOT NULL
);

WHERE with Subqueries

Subqueries in WHERE let you filter based on data from other tables or computed results. There are three main patterns.

Scalar subquery (returns one value)

-- Find employees earning more than the company average
SELECT name, salary FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
 name        | salary
-------------+---------
 Alice Chen  | 95000
 Charlie Kim | 88000
 Eve Johnson | 105000
 Henry Davis | 92000

IN with subquery (returns a list)

-- Find employees who are managers
SELECT name, department FROM employees
WHERE id IN (
    SELECT DISTINCT manager_id
    FROM employees
    WHERE manager_id IS NOT NULL
);
 name        | department
-------------+-------------
 Alice Chen  | Engineering
 Bob Martinez| Marketing
 Diana Patel | Sales
 Eve Johnson | Engineering

EXISTS (correlated subquery)

EXISTS returns true if the subquery returns at least one row. It is often faster than IN because it stops searching at the first match:

-- Find departments that have at least one employee earning over 90k
SELECT DISTINCT department FROM employees e1
WHERE EXISTS (
    SELECT 1 FROM employees e2
    WHERE e2.department = e1.department
      AND e2.salary > 90000
);
 department
-------------
 Engineering

NOT EXISTS

-- Find employees who are NOT managers (nobody reports to them)
SELECT name, department FROM employees e
WHERE NOT EXISTS (
    SELECT 1 FROM employees m
    WHERE m.manager_id = e.id
);
 name        | department
-------------+------------
 Charlie Kim | Engineering
 Diana Patel | Sales
 Frank Lee   | Marketing
 Grace Wu    | Sales
 Henry Davis | Engineering

NOT EXISTS is the safest way to do anti-joins. Unlike NOT IN, it handles NULLs correctly without extra filtering.

WHERE vs HAVING

WHERE and HAVING both filter data, but they operate at different stages of query execution.

Feature WHERE HAVING
Filters Individual rows Groups (after GROUP BY)
Runs Before GROUP BY After GROUP BY
Aggregates Cannot use COUNT, SUM, etc. Can use aggregate functions
Example WHERE salary > 50000 HAVING COUNT(*) > 3

You can use both in the same query. WHERE filters rows first, then GROUP BY groups them, then HAVING filters the groups:

SELECT
    department,
    COUNT(*) AS headcount,
    AVG(salary) AS avg_salary
FROM employees
WHERE hire_date >= '2023-01-01'     -- filter rows first
GROUP BY department
HAVING COUNT(*) >= 2                 -- then filter groups
ORDER BY avg_salary DESC;
 department  | headcount | avg_salary
-------------+-----------+------------
 Engineering |         4 |   95000.00
 Marketing   |         2 |   66500.00
 Sales       |         2 |   72500.00

For a deep dive into grouping and aggregation, see the SQL GROUP BY guide.

WHERE Clause Performance Tips

A poorly written WHERE clause can turn a millisecond query into one that takes minutes. Here are the key principles for fast filtering.

1. Index the columns you filter on

-- Create an index on columns used in WHERE
CREATE INDEX idx_employees_department ON employees(department);
CREATE INDEX idx_employees_hire_date ON employees(hire_date);

-- Composite index for queries that filter on multiple columns
CREATE INDEX idx_employees_dept_salary
ON employees(department, salary);

An index lets the database jump directly to matching rows instead of scanning the entire table. On a million-row table, this can be 1000x faster.

2. Write sargable predicates

"Sargable" means the database can use an index to satisfy the condition. Avoid wrapping indexed columns in functions:

-- NOT sargable: function on column prevents index use
WHERE YEAR(hire_date) = 2024
WHERE UPPER(department) = 'ENGINEERING'
WHERE salary + 1000 > 90000

-- Sargable: index can be used
WHERE hire_date >= '2024-01-01' AND hire_date < '2025-01-01'
WHERE department = 'Engineering'
WHERE salary > 89000

3. Use EXPLAIN to verify index usage

EXPLAIN ANALYZE
SELECT name, salary FROM employees
WHERE department = 'Engineering' AND salary > 90000;

-- Good: "Index Scan" or "Index Seek"
-- Bad: "Seq Scan" or "Table Scan" on a large table

4. Put the most selective condition first

Most query optimizers handle this automatically, but it helps readability. Place the condition that eliminates the most rows first:

-- Good: id = 42 is highly selective (one row)
WHERE id = 42 AND status = 'active'

-- Less ideal for readability (status matches many rows)
WHERE status = 'active' AND id = 42

5. Prefer EXISTS over IN for large subqueries

-- Slower on large datasets
WHERE customer_id IN (SELECT id FROM customers WHERE country = 'US')

-- Faster: stops at first match
WHERE EXISTS (
    SELECT 1 FROM customers c
    WHERE c.id = orders.customer_id AND c.country = 'US'
)

6. Avoid OR on different columns (use UNION instead)

-- OR on different columns often prevents index use
SELECT name FROM employees
WHERE department = 'Sales' OR salary > 100000;

-- UNION allows each branch to use its own index
SELECT name FROM employees WHERE department = 'Sales'
UNION
SELECT name FROM employees WHERE salary > 100000;

Common WHERE Mistakes

These are the mistakes that show up again and again in code reviews and debugging sessions.

1. Using = NULL instead of IS NULL

-- WRONG: always returns zero rows
SELECT * FROM employees WHERE email = NULL;

-- CORRECT
SELECT * FROM employees WHERE email IS NULL;

2. Forgetting quotes around strings

-- ERROR: SQL thinks Engineering is a column name
SELECT * FROM employees WHERE department = Engineering;

-- CORRECT: strings must be in single quotes
SELECT * FROM employees WHERE department = 'Engineering';

3. OR without parentheses

-- BUG: AND binds tighter than OR
SELECT * FROM employees
WHERE department = 'Sales' OR department = 'Marketing'
  AND salary > 70000;
-- Returns ALL Sales + only Marketing with salary > 70k

-- FIX: use parentheses
SELECT * FROM employees
WHERE (department = 'Sales' OR department = 'Marketing')
  AND salary > 70000;
-- Returns Sales or Marketing employees with salary > 70k

4. LIKE without wildcards

-- This is just a slower version of =
WHERE name LIKE 'Alice Chen'

-- If you want exact match, use =
WHERE name = 'Alice Chen'

-- LIKE only makes sense with % or _
WHERE name LIKE 'Alice%'

5. Leading wildcard kills performance

-- Cannot use an index (must scan every row)
WHERE email LIKE '%@gmail.com'

-- CAN use an index (starts with a fixed prefix)
WHERE email LIKE 'alice%'

If you need to search for patterns in the middle or end of strings frequently, consider full-text search indexes or a reverse-index approach.

6. NOT IN with NULLs

-- Returns NOTHING if subquery contains any NULL
WHERE id NOT IN (SELECT manager_id FROM employees)

-- Safe alternatives
WHERE id NOT IN (SELECT manager_id FROM employees WHERE manager_id IS NOT NULL)
-- or
WHERE NOT EXISTS (SELECT 1 FROM employees e2 WHERE e2.manager_id = e.id)

7. Using column aliases in WHERE

-- ERROR: WHERE runs before SELECT, so aliases don't exist yet
SELECT salary * 1.1 AS new_salary
FROM employees
WHERE new_salary > 100000;

-- FIX: repeat the expression
SELECT salary * 1.1 AS new_salary
FROM employees
WHERE salary * 1.1 > 100000;

-- Or use a subquery / CTE
WITH raised AS (
    SELECT name, salary * 1.1 AS new_salary FROM employees
)
SELECT * FROM raised WHERE new_salary > 100000;

Real-World WHERE Patterns

Here are five patterns you will use constantly in production applications and reports.

1. Active users in the last 30 days

SELECT
    user_id,
    email,
    last_login
FROM users
WHERE last_login >= CURRENT_DATE - INTERVAL '30 days'
  AND status = 'active'
ORDER BY last_login DESC;

This is the most common pattern in SaaS analytics. The date arithmetic syntax varies by database: INTERVAL '30 days' (PostgreSQL), DATE_SUB(CURDATE(), INTERVAL 30 DAY) (MySQL), DATEADD(day, -30, GETDATE()) (SQL Server).

2. Orders in a date range with status filter

SELECT
    o.id AS order_id,
    c.name AS customer,
    o.total_amount,
    o.status,
    o.created_at
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE o.created_at BETWEEN '2026-01-01' AND '2026-03-31'
  AND o.status IN ('completed', 'shipped')
  AND o.total_amount > 50
ORDER BY o.created_at DESC;

Combining a date range, an IN list, and a comparison is standard for order reports. See the SQL Joins guide for more on combining joins with filters.

3. Products in a price range by category

SELECT
    name,
    category,
    price,
    stock_quantity
FROM products
WHERE category IN ('Electronics', 'Accessories')
  AND price BETWEEN 25.00 AND 200.00
  AND stock_quantity > 0
ORDER BY price ASC;

4. Employees hired this year

SELECT
    name,
    department,
    hire_date,
    salary
FROM employees
WHERE hire_date >= DATE_TRUNC('year', CURRENT_DATE)
ORDER BY hire_date;

DATE_TRUNC('year', CURRENT_DATE) returns January 1st of the current year. In MySQL, use WHERE hire_date >= MAKEDATE(YEAR(CURDATE()), 1).

5. Finding records with missing data

SELECT
    id,
    name,
    email,
    phone,
    address
FROM customers
WHERE email IS NULL
   OR phone IS NULL
   OR address IS NULL
ORDER BY id;

Data quality checks like this are essential before sending marketing campaigns, generating invoices, or running reports. You can also count the gaps:

SELECT
    COUNT(*) AS total_customers,
    COUNT(*) - COUNT(email) AS missing_email,
    COUNT(*) - COUNT(phone) AS missing_phone,
    COUNT(*) - COUNT(address) AS missing_address
FROM customers;

COUNT(*) counts all rows, while COUNT(column) counts non-NULL values. The difference gives you the number of NULLs.

For more practical query patterns, check the SQL query examples collection and the SQL cheat sheet.

Skip writing WHERE clauses by hand. Describe your filter in plain English and AI2SQL generates the correct SQL for your database dialect.

Frequently Asked Questions

What does the WHERE clause do in SQL?

The WHERE clause filters rows returned by a SQL query. It evaluates a condition for each row and only includes rows where the condition is true. WHERE works with SELECT, UPDATE, and DELETE statements. It runs before GROUP BY and SELECT, so it filters individual rows before any aggregation happens.

What is the difference between WHERE and HAVING in SQL?

WHERE filters individual rows before grouping. HAVING filters groups after GROUP BY has been applied. Use WHERE for conditions on raw column values (e.g., WHERE status = 'active'). Use HAVING for conditions on aggregate functions (e.g., HAVING COUNT(*) > 5). You can use both in the same query: WHERE filters rows first, then GROUP BY groups them, then HAVING filters the groups.

Why does WHERE column = NULL not work?

In SQL, NULL represents an unknown value. Any comparison with NULL using =, <>, <, or > returns NULL (not true or false), so the row is excluded. You must use IS NULL or IS NOT NULL instead. For example: WHERE email IS NULL finds rows with no email, while WHERE email = NULL returns nothing.

How do I filter text with wildcards in SQL WHERE?

Use the LIKE operator with wildcard characters. The percent sign (%) matches any sequence of characters, and the underscore (_) matches exactly one character. For example: WHERE name LIKE 'J%' finds names starting with J, WHERE email LIKE '%@gmail.com' finds Gmail addresses, and WHERE code LIKE 'A_B' matches A followed by any single character followed by B. In PostgreSQL, use ILIKE for case-insensitive matching.

Can AI generate SQL WHERE clauses for me?

Yes. Tools like AI2SQL let you describe your filter conditions in plain English, and the AI generates the correct WHERE clause automatically. This is especially helpful for complex conditions combining AND, OR, subqueries, and date ranges. You describe what rows you want, and get working SQL in seconds without memorizing operator syntax. Try AI2SQL free.

Generate SQL WHERE Clauses from Plain English

Stop struggling with complex filter conditions. Describe the data you need and let AI2SQL generate the correct WHERE clause for your database.

Try AI2SQL Free

No credit card required