Loading learning content...
Exact equality isn't always what you need. Find products containing 'wireless'. List customers whose names start with 'John'. Show emails from any '.edu' domain. These queries require pattern matching—the ability to specify what a string should look like rather than what it should exactly be.
The LIKE operator is SQL's built-in pattern matching tool, using wildcard characters to specify flexible match criteria. While simple in concept, LIKE has nuances in syntax, performance, and behavior that separate casual users from experts.
By the end of this page, you will master LIKE's wildcard syntax, understand when patterns can (and cannot) use indexes, handle special characters with escape sequences, and know when to reach for more powerful pattern matching tools like SIMILAR TO or regular expressions.
LIKE compares a string value against a pattern containing special wildcard characters. The basic syntax is:
column LIKE 'pattern'
Two Wildcard Characters:
| Wildcard | Meaning | Matches Examples |
|---|---|---|
% (percent) | Zero or more characters | '%test%' matches 'test', 'testing', 'a test case', 'atestb' |
_ (underscore) | Exactly one character | '_est' matches 'test', 'best', 'rest'; NOT 'est' or 'atest' |
1234567891011121314151617181920212223
-- Prefix matching: Starts withSELECT * FROM products WHERE name LIKE 'iPhone%';-- Matches: iPhone, iPhone 15, iPhone 15 Pro Max -- Suffix matching: Ends withSELECT * FROM users WHERE email LIKE '%@gmail.com';-- Matches: john@gmail.com, test.user@gmail.com -- Contains: Anywhere in stringSELECT * FROM articles WHERE title LIKE '%database%';-- Matches: Database Design, The database layer, building databases -- Single character wildcardSELECT * FROM products WHERE sku LIKE 'A_C';-- Matches: ABC, A1C, A-C-- Does NOT match: AC, ABBC, A12C -- Combining wildcardsSELECT * FROM users WHERE phone LIKE '+1-___-___-____';-- Matches: +1-555-123-4567 format SELECT * FROM products WHERE code LIKE 'PRD_____';-- Products with code starting 'PRD' followed by exactly 5 charactersPattern Placement:
| Pattern | Description | Use Case |
|---|---|---|
'prefix%' | Starts with 'prefix' | Autocomplete, prefix search |
'%suffix' | Ends with 'suffix' | File extensions, domain matching |
'%contains%' | Contains 'contains' anywhere | Keyword search, filtering |
'exact' | Exact match (no wildcards) | Equivalent to = but slower |
'a_b' | Three chars: a, any, b | Format validation |
'%\_test%' | Contains literal underscore | Escape sequence usage |
NOT LIKE finds strings that do NOT match a pattern. It's the logical negation of LIKE.
Basic Usage:
12345678910111213141516
-- Exclude specific patternsSELECT * FROM users WHERE email NOT LIKE '%@spam.com';-- Users not from spam.com SELECT * FROM products WHERE name NOT LIKE 'OBSOLETE%';-- Products not marked obsolete SELECT * FROM logs WHERE message NOT LIKE '%DEBUG%';-- Non-debug log entries -- Combine with other conditionsSELECT * FROM customers WHERE name NOT LIKE 'Test%' AND name NOT LIKE '%DELETE%' AND email NOT LIKE '%@example.com';-- Exclude test data patternsLike all comparison operators, NOT LIKE returns UNKNOWN when applied to NULL. If name is NULL, name NOT LIKE '%test%' is UNKNOWN (not TRUE), so the row is excluded. Handle NULLs explicitly if needed: name NOT LIKE '%test%' OR name IS NULL.
1234567891011121314
-- NOT LIKE excludes NULLs silentlySELECT * FROM products WHERE description NOT LIKE '%special%';-- Excludes: products containing 'special'-- Also excludes: products where description IS NULL (often unintended) -- Include NULLs when they should passSELECT * FROM products WHERE description NOT LIKE '%discontinued%' OR description IS NULL;-- Products not marked discontinued, including those with no description -- Alternative with COALESCESELECT * FROM products WHERE COALESCE(description, '') NOT LIKE '%discontinued%';-- Treats NULL as empty stringLIKE's case sensitivity depends on the column's collation—the rules that determine how strings are compared. This varies by database and configuration.
Database-Specific Behavior:
| Database | Default Behavior | Case-Insensitive Option |
|---|---|---|
| PostgreSQL | Case-Sensitive | Use ILIKE operator |
| MySQL (default) | Case-Insensitive | Use BINARY keyword or binary collation |
| SQL Server | Depends on collation | Case-insensitive collations are common |
| Oracle | Case-Sensitive | Use UPPER() or LOWER() |
| SQLite | Case-Insensitive for ASCII | Only for ASCII letters by default |
12345678910111213141516171819202122232425
-- PostgreSQL: LIKE is case-sensitiveSELECT * FROM products WHERE name LIKE '%iphone%';-- Matches: 'iphone', 'My iphone'-- Does NOT match: 'iPhone', 'IPHONE' -- PostgreSQL: ILIKE for case-insensitiveSELECT * FROM products WHERE name ILIKE '%iphone%';-- Matches: 'iPhone', 'iphone', 'IPHONE', 'IpHoNe' -- MySQL: LIKE is case-insensitive by defaultSELECT * FROM products WHERE name LIKE '%iphone%';-- Matches: 'iPhone', 'iphone', 'IPHONE' (all) -- MySQL: Force case-sensitive with BINARYSELECT * FROM products WHERE name LIKE BINARY '%iPhone%';-- Only matches exact case -- Portable case-insensitive patternSELECT * FROM products WHERE LOWER(name) LIKE LOWER('%iPhone%');-- Works everywhere but may not use indexes -- Better performance: Normalize data on insert-- Store name_lower as a computed/generated column-- CREATE INDEX idx_name_lower ON products(name_lower);SELECT * FROM products WHERE name_lower LIKE 'iphone%';With accent-insensitive collations, 'café' might match 'cafe'. With case-insensitive collations, 'ABC' matches 'abc'. Know your collation settings to predict LIKE behavior accurately.
What if you need to search for a literal % or _ character? Since these are wildcards, they need to be escaped when you want to match them literally.
The ESCAPE Clause:
12345678910111213141516171819
-- Problem: Find codes containing underscoreSELECT * FROM products WHERE code LIKE '%_%';-- This matches EVERYTHING (underscore matches any single character) -- Solution: Use ESCAPE clauseSELECT * FROM products WHERE code LIKE '%\_%' ESCAPE '\';-- The backslash escapes the underscore-- Matches: 'A_B', 'CODE_123', 'TEST_ITEM' -- Escape the percent signSELECT * FROM discounts WHERE description LIKE '%50\%%' ESCAPE '\';-- Matches: '50% off', 'Save 50%!' -- Choose any escape characterSELECT * FROM codes WHERE code LIKE '%|_%' ESCAPE '|';-- Using | as escape character SELECT * FROM patterns WHERE pattern LIKE '%!%%' ESCAPE '!';-- Using ! as escape characterEscaping the Escape Character:
If you need to match the escape character itself, double it:
12345678910111213141516171819202122
-- Match literal backslashSELECT * FROM paths WHERE path LIKE '%\\%' ESCAPE '\';-- Matches paths containing backslash-- Note: Some databases may require additional escaping for string literals -- Match backslash followed by underscoreSELECT * FROM data WHERE text LIKE '%\\_char%' ESCAPE '\';-- Matches: 'has\_char_here' -- Database-specific escape handling-- PostgreSQL: Standard ESCAPE clause worksSELECT * FROM products WHERE name LIKE '%_%' ESCAPE ''; -- SQL Server: Square brackets can escapeSELECT * FROM products WHERE name LIKE '%[_]%';-- [_] matches literal underscoreSELECT * FROM products WHERE name LIKE '%[%]%';-- [%] matches literal percent -- MySQL: Default escape is backslashSELECT * FROM products WHERE name LIKE '%\_%';-- Backslash escape is default, ESCAPE clause optionalWhen building LIKE patterns from user input, escape wildcard characters to prevent unintended matching. If a user searches for '50%', you need to escape the percent sign or they'll match far more than intended. Use parameterized patterns or escape functions provided by your database driver.
1234567891011121314
-- Building safe LIKE patterns from user input-- Pseudocode for application layer: -- user_input = "50% off"-- escaped_input = user_input.replace('%', '\%').replace('_', '\_')-- query = "SELECT * FROM products WHERE description LIKE '%' + escaped_input + '%' ESCAPE '\'" -- Example in PostgreSQL with parameterPREPARE find_text(text) AS SELECT * FROM products WHERE description LIKE '%' || replace(replace($1, '%', '\%'), '_', '\_') || '%' ESCAPE '\'; EXECUTE find_text('50% off');-- Correctly searches for literal "50% off"LIKE's performance depends critically on where the wildcard appears. This determines whether indexes can be used.
The Prefix Rule:
A B-tree index can only be used when the pattern has a fixed prefix (no leading wildcard).
| Pattern | Index Usable? | Reason |
|---|---|---|
'prefix%' | ✅ Yes | Fixed prefix enables index range scan |
'%suffix' | ❌ No | Leading wildcard requires full scan |
'%contains%' | ❌ No | Leading wildcard requires full scan |
'pre%fix' | Partial | Uses index for 'pre', filters rest |
'exact' | ✅ Yes | Equivalent to = (equality) |
'_BC%' | ❌ No | Leading _ is still a leading wildcard |
1234567891011121314151617181920212223242526
-- Create index for these examplesCREATE INDEX idx_products_name ON products(name); -- INDEXABLE: Prefix patternEXPLAIN SELECT * FROM products WHERE name LIKE 'iPhone%';-- Uses: Index range scan on idx_products_name-- Execution: Seek to 'iPhone', scan while name < 'iPhonf' -- NOT INDEXABLE: Suffix patternEXPLAIN SELECT * FROM products WHERE name LIKE '%Pro';-- Uses: Sequential scan (table scan)-- Must examine every row -- NOT INDEXABLE: Contains patternEXPLAIN SELECT * FROM products WHERE name LIKE '%wireless%';-- Uses: Sequential scan-- No way to use index for arbitrary substring -- PARTIAL USE: Pattern with fixed prefixEXPLAIN SELECT * FROM products WHERE name LIKE 'Apple%Watch%';-- Uses: Index range scan for 'Apple%'-- Then filters: remaining rows for '%Watch%' -- Verify with EXPLAIN ANALYZEEXPLAIN ANALYZE SELECT * FROM products WHERE name LIKE 'Sam%';-- Shows actual rows scanned vs. returnedSolutions for Suffix/Contains Searches:
1234567891011121314151617181920212223242526272829
-- SOLUTION 1: Full-Text Search (for contains)-- PostgreSQLCREATE INDEX idx_products_fts ON products USING gin(to_tsvector('english', name));SELECT * FROM products WHERE to_tsvector('english', name) @@ to_tsquery('wireless'); -- MySQLALTER TABLE products ADD FULLTEXT INDEX ft_name (name);SELECT * FROM products WHERE MATCH(name) AGAINST('wireless'); -- SOLUTION 2: Trigram indexes (PostgreSQL)CREATE EXTENSION IF NOT EXISTS pg_trgm;CREATE INDEX idx_products_name_trgm ON products USING gin(name gin_trgm_ops);SELECT * FROM products WHERE name LIKE '%wireless%';-- Now uses the trigram index! -- SOLUTION 3: Reverse index for suffix searches-- Store both name and REVERSE(name)ALTER TABLE products ADD COLUMN name_reversed VARCHAR(255);UPDATE products SET name_reversed = REVERSE(name);CREATE INDEX idx_name_reversed ON products(name_reversed); -- Suffix search becomes prefix search on reversed:SELECT * FROM products WHERE name_reversed LIKE REVERSE('%Pro');-- Equivalent to: WHERE name_reversed LIKE 'orP%' -- SOLUTION 4: Computed/generated column with normalized dataALTER TABLE products ADD COLUMN name_lower VARCHAR(255) GENERATED ALWAYS AS (LOWER(name)) STORED;CREATE INDEX idx_name_lower ON products(name_lower);For searching within text content (blog posts, descriptions, documents), full-text search is vastly superior to LIKE '%term%'. It's faster, supports stemming (matching 'running' when searching 'run'), and provides relevance ranking.
While LIKE is limited to % and _ wildcards, creative usage can solve surprisingly complex matching needs.
Multiple LIKE Conditions:
12345678910111213141516171819202122232425
-- Multiple patterns with ORSELECT * FROM products WHERE name LIKE 'iPhone%' OR name LIKE 'iPad%' OR name LIKE 'Mac%';-- Apple products starting with known prefixes -- All conditions with ANDSELECT * FROM customers WHERE name LIKE 'John%' AND email LIKE '%@gmail.com';-- Johns with Gmail addresses -- Exclusion patternsSELECT * FROM products WHERE name LIKE '%Pro%' AND name NOT LIKE '%Prototype%' AND name NOT LIKE '%Process%';-- Products with 'Pro' but not those compound words -- Complex multi-pattern filteringSELECT * FROM error_logs WHERE (message LIKE '%ERROR%' OR message LIKE '%FATAL%') AND message NOT LIKE '%DEBUG%' AND message NOT LIKE '%TEST%';Format Validation with Underscore:
12345678910111213141516171819
-- Fixed-length format validationSELECT * FROM products WHERE sku LIKE '___-___-____';-- Matches: ABC-123-4567 (3 chars, dash, 3 chars, dash, 4 chars) -- Credit card format (obfuscated example)SELECT * FROM payments WHERE card_display LIKE '****-****-****-____';-- Matches: ****-****-****-1234 -- License plate patternsSELECT * FROM vehicles WHERE plate LIKE '___-____';-- Matches: ABC-1234 -- Date-like format (not recommended for actual dates)SELECT * FROM legacy_data WHERE date_string LIKE '____-__-__';-- Matches: 2024-01-15 format -- Combining fixed and variable partsSELECT * FROM serial_numbers WHERE serial LIKE 'SN-____-%';-- Matches: SN-1234-anything, SN-5678-moreCharacter Class Simulation (SQL Server):
SQL Server's LIKE supports a limited form of character classes using square brackets:
1234567891011121314151617181920
-- SQL Server: Character ranges with []SELECT * FROM products WHERE code LIKE '[A-Z][0-9][0-9][0-9]';-- Matches: A123, B456, Z999 (letter followed by 3 digits) SELECT * FROM users WHERE username LIKE '[a-zA-Z]%';-- Usernames starting with a letter SELECT * FROM codes WHERE code LIKE '[0-9][0-9][0-9]-[A-Z][A-Z]';-- Matches: 123-AB, 999-ZZ -- Negation with ^SELECT * FROM data WHERE value LIKE '%[^0-9]%';-- Values containing at least one non-digit SELECT * FROM codes WHERE code LIKE '[^X-Z]%';-- Codes NOT starting with X, Y, or Z -- Character setSELECT * FROM products WHERE code LIKE '[ABC]%';-- Starting with A, B, or C onlyWhen LIKE's simple wildcards aren't enough, SQL offers more powerful pattern matching tools.
SIMILAR TO (SQL Standard):
SIMILAR TO is a hybrid of LIKE and regular expressions, supported by PostgreSQL and some other databases:
12345678910111213141516171819202122
-- SIMILAR TO syntax (PostgreSQL)-- Uses SQL-style regex: % and _ plus regex features -- Alternation with |SELECT * FROM products WHERE name SIMILAR TO '(iPhone|iPad|Mac)%';-- Starting with iPhone, iPad, or Mac -- Repetition with * + ?SELECT * FROM codes WHERE code SIMILAR TO '[A-Z]+[0-9]*';-- One or more letters, then zero or more digits -- Character classesSELECT * FROM data WHERE value SIMILAR TO '[0-9]{3}-[0-9]{4}';-- Matches: 123-4567 -- Combining patternsSELECT * FROM emails WHERE address SIMILAR TO '[a-z]+@[a-z]+\.(com|org|net)';-- Simple email pattern -- SIMILAR TO uses % for zero-or-more characters (like LIKE)SELECT * FROM products WHERE name SIMILAR TO 'Pro%Book';-- 'ProBook', 'ProductBook', 'ProMacBook'Regular Expressions:
For full regex power, databases provide special operators:
123456789101112131415161718192021222324252627282930
-- PostgreSQL: ~ operator (case-sensitive regex match)SELECT * FROM emails WHERE address ~ '^[a-z]+@[a-z]+\.[a-z]{2,}$';-- Full email validation pattern SELECT * FROM emails WHERE address ~* 'gmail\.com$';-- ~* is case-insensitive -- Match any of several patternsSELECT * FROM products WHERE name ~ '(wireless|bluetooth|wifi)';-- Contains wireless, bluetooth, or wifi -- PostgreSQL: !~ for NOT matchingSELECT * FROM logs WHERE message !~ '(DEBUG|TRACE)';-- Exclude debug and trace messages -- MySQL: REGEXP or RLIKESELECT * FROM products WHERE name REGEXP '^(iPhone|iPad)';-- Starting with iPhone or iPad SELECT * FROM emails WHERE address REGEXP '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}';-- Email pattern -- Oracle: REGEXP_LIKE functionSELECT * FROM products WHERE REGEXP_LIKE(name, '^(iPhone|iPad)', 'i');-- 'i' flag for case-insensitive -- SQL Server: PATINDEX or CLR functions-- Native regex not available; use PATINDEX for limited patternsSELECT * FROM products WHERE PATINDEX('%[0-9]%', name) > 0;-- Names containing at least one digitRegular expression matching is generally slower than LIKE and rarely uses indexes (except with special index types like trigram in PostgreSQL). For high-volume queries, consider pre-computing matches, using full-text search, or filtering with indexed LIKE first, then applying regex.
Following established best practices ensures your LIKE queries are correct, performant, and maintainable.
'prefix%' can use indexes; '%suffix' cannotLOWER(name) LIKE 'search%', add a name_lower column with an index1234567891011121314151617181920212223
-- GOOD: Indexed prefix searchSELECT * FROM customers WHERE name LIKE 'Smith%'; -- BAD: Unindexed contains search on large tableSELECT * FROM documents WHERE content LIKE '%important%';-- BETTER: Use full-text searchSELECT * FROM documents WHERE to_tsvector(content) @@ to_tsquery('important'); -- GOOD: Escaped user input-- Application builds: userInput = "50%"-- escapedInput = "50\%"SELECT * FROM products WHERE name LIKE '%' || :escapedInput || '%' ESCAPE '\'; -- GOOD: Explicit NULL handlingSELECT * FROM products WHERE (description LIKE '%keyword%' OR description IS NULL); -- GOOD: Case-insensitive with ILIKE (PostgreSQL)SELECT * FROM products WHERE name ILIKE '%wireless%'; -- ALTERNATIVE: Pre-normalized columnSELECT * FROM products WHERE name_searchable LIKE '%wireless%';-- Where name_searchable = LOWER(UNACCENT(name))The LIKE operator provides essential pattern matching capabilities, and understanding its nuances makes the difference between effective searches and performance disasters.
You have now completed a comprehensive exploration of the WHERE clause—SQL's fundamental mechanism for filtering data. Let's consolidate the module's key concepts:
You now possess deep understanding of the WHERE clause—the foundational tool for data filtering in SQL. These skills form the backbone of effective query writing, enabling you to retrieve precisely the data you need while maintaining performance at scale.