SQL Like
The LIKE operator searches for a pattern inside a text column instead of requiring an exact match. Instead of writing WHERE name = 'Alice', you can write WHERE name LIKE 'A%' to find every name that starts with the letter A. It is the standard SQL tool for partial-text matching — names, emails, addresses, product codes — and it works, with minor differences, across SQLite, MySQL, PostgreSQL, and SQL Server.
Overview: How LIKE Works Under the Hood
LIKE compares a column (or expression) against a pattern string that may contain two special wildcard characters: %, which matches zero or more of any character, and _, which matches exactly one character. Everything else in the pattern is matched literally, character by character. The database engine walks the pattern and the target string together: literal characters must match exactly, a _ consumes one character of input, and a % lets the engine skip ahead any number of characters before continuing to match the rest of the pattern.
Where LIKE sits in query processing matters. Like any other condition, a LIKE predicate in a WHERE clause is evaluated after the FROM clause has produced rows (including joins) and before GROUP BY, HAVING, SELECT, and ORDER BY run. A row is discarded before it ever reaches aggregation or column selection if it fails the LIKE test.
Performance depends heavily on where the wildcard sits. A pattern with no leading wildcard, like 'Sm%', can use a standard B-tree index the same way a range scan does, because the engine knows matching values must sort between Sm and Sn. A pattern that starts with a wildcard, like '%son', cannot use that index for narrowing and generally forces a full table or full index scan, since matching rows could start with any character. This is why starts-with searches stay cheap at scale while contains searches do not, unless the database has a specialized text or trigram index.
Case sensitivity also varies by engine. SQLite’s LIKE is case-insensitive for ASCII letters by default, so 'alice' matches 'ALICE'. PostgreSQL’s LIKE is case-sensitive by default — use ILIKE there for case-insensitive matching. MySQL’s behavior depends on the column’s collation, which is case-insensitive in most default collations. Always check your specific engine and collation rather than assuming behavior carries over.
Syntax
SELECT column_list
FROM table_name
WHERE column_name LIKE pattern [ESCAPE escape_character];
| Part | Meaning |
|---|---|
LIKE |
Tests whether the column value matches the given pattern |
NOT LIKE |
Negation — matches values that do not fit the pattern |
% |
Wildcard matching zero or more of any character |
_ |
Wildcard matching exactly one of any character |
ESCAPE 'char' |
Declares a character that, placed before % or _ in the pattern, makes them literal instead of wildcards |
Patterns are always written as string literals in single quotes. A pattern with no % or _ at all behaves exactly like =.
Examples
Example 1: Starts-with matching
SELECT customer_name, city
FROM customers
WHERE customer_name LIKE 'A%'
ORDER BY customer_name;
Result:
| customer_name | city |
|---|---|
| Alice Johnson | New York |
| Anna Martinez | Austin |
The % after A matches any number of characters, so both Alice Johnson and Anna Martinez qualify because their names begin with the letter A. Bob Smith, Carla Ortiz, and David Lee are excluded because their first character is not A.
Example 2: Contains matching
SELECT customer_name, email
FROM customers
WHERE email LIKE '%gmail%'
ORDER BY customer_name;
Result:
| customer_name | |
|---|---|
| Alice Johnson | alice.j@gmail.com |
| Anna Martinez | anna.m@gmail.com |
| Carla Ortiz | carla_o@gmail.com |
Putting % on both sides of gmail means the engine accepts any prefix and any suffix, so it finds gmail anywhere in the string. This is the classic contains search, and it is the pattern most likely to force a full table scan since there is no fixed starting point for an index to anchor on.
Example 3: Combining LIKE with other conditions
SELECT customer_name, city
FROM customers
WHERE city LIKE 'New%'
AND customer_name LIKE 'A%';
Result:
| customer_name | city |
|---|---|
| Alice Johnson | New York |
Both New York and New Orleans match city LIKE 'New%', but only Alice Johnson’s name also starts with A, so David Lee (New Orleans) is filtered out by the second condition. This shows how LIKE combines with AND and OR just like any other boolean condition.
Example 4: The single-character wildcard and ESCAPE
SELECT customer_name
FROM customers
WHERE customer_name LIKE '_ob%';
Result:
| customer_name |
|---|
| Bob Smith |
The underscore matches exactly one character, so _ob% means: any single character, then literally ob, then anything. Only Bob Smith fits. If you need to search for a literal underscore or percent sign that is actually part of the data — such as the underscore in an email address — escape it:
SELECT customer_name, email
FROM customers
WHERE email LIKE '%carla\_o%' ESCAPE '\';
Result:
| customer_name | |
|---|---|
| Carla Ortiz | carla_o@gmail.com |
The ESCAPE '\' clause tells the engine that a backslash before a wildcard character means match this character literally. Without it, the underscore in carla_o would still act as a wildcard matching any single character, which happens to work here but would also incorrectly match unintended strings like carlaXo.
How It Works Step by Step
For a query like SELECT customer_name FROM customers WHERE customer_name LIKE 'A%' ORDER BY customer_name;, the engine conceptually proceeds in this order:
- FROM — build the working row set from
customers(and resolve any joins, if present). - WHERE — evaluate
customer_name LIKE 'A%'for every row; keep only rows where the pattern matches. - GROUP BY / HAVING — skipped here, since the query has no aggregation.
- SELECT — project just the
customer_namecolumn from the surviving rows. - ORDER BY — sort the final result set alphabetically by
customer_name.
Internally, the pattern-matching step itself behaves like a small state machine: the engine walks the pattern left to right, and for each literal character it must find an exact match at the current position; for _ it advances one position unconditionally; for % it tries matching the rest of the pattern at every possible remaining position until one succeeds or all fail. That is also why patterns with multiple % wildcards, or % combined with long strings, can be more expensive to evaluate than a simple prefix match.
Common Mistakes
Mistake 1: Forgetting to quote the pattern
SELECT customer_name
FROM customers
WHERE customer_name LIKE A%;
Without quotes, A% is not a valid string literal — the parser does not know what to do with the bare token A followed by a bare %, and raises a syntax error. Patterns must always be quoted strings:
SELECT customer_name
FROM customers
WHERE customer_name LIKE 'A%';
Mistake 2: Using filesystem-style wildcards
Many people coming from command-line tools instinctively reach for * and ?, but SQL’s LIKE does not use those — it uses % and _.
-- Wrong: '*' is treated as a literal character, not a wildcard
SELECT customer_name FROM customers WHERE customer_name LIKE 'A*';
This silently returns zero rows instead of raising an error, because no customer name actually contains the literal characters A followed by an asterisk — it is easy to miss this bug since there is no error message to flag it. The fix is to use SQL’s own wildcard syntax:
-- Correct: '%' matches any sequence of characters
SELECT customer_name FROM customers WHERE customer_name LIKE 'A%';
Best Practices
- Prefer a leading literal (
'Sm%') over a leading wildcard ('%th') whenever possible — it lets the query planner use an index instead of scanning every row. - Use
ESCAPEwhenever your search term might itself contain a literal%or_, such as searching for a percent sign or an email address with an underscore. - Do not assume case sensitivity is the same across engines — test explicitly, or normalize both sides with
UPPER()orLOWER()if you need guaranteed behavior. - For heavy contains or fuzzy-text searches at scale, consider a dedicated full-text search index instead of
LIKE '%term%', since it cannot use a normal index efficiently. - Combine
LIKEwith other filters (AND/OR) to narrow the row set as early as possible, reducing how many rows need pattern matching. - Prefer
NOT LIKEover wrappingLIKEinNOT (...)for readability — both work, butNOT LIKEreads more naturally.
Practice Exercises
Using a customers table with columns customer_name, email, and city (as in the examples above):
- Write a query that finds every customer whose city ends in the letter n (for example, Boston).
- Write a query that finds every customer whose email address does not come from a gmail.com address.
- Write a query that finds every customer whose name has a space followed by a word starting with the letter S (hint: think about how
%and a literal space combine, e.g.'% S%').
Summary
LIKEmatches text against a pattern using%(any number of characters) and_(exactly one character).NOT LIKEnegates the match, returning rows that do not fit the pattern.- Patterns with a leading wildcard (
'%text') generally cannot use an index and force a full scan; leading-literal patterns ('text%') can. - Use
ESCAPEto search for a literal%or_that is part of the actual data. - Case sensitivity of
LIKEvaries by database engine — check before assuming. LIKEis evaluated in theWHEREclause, beforeGROUP BY,SELECT, andORDER BYrun.
