SQL cho phân tích dữ liệu · Hướng dẫn · 30 tháng 10, 2026 · 13 phút đọc
Lệnh LIKE trong SQL: ký tự đại diện % và _, ILIKE, NOT LIKE và tìm tiếng Việt có dấu

LIKE lọc chuỗi theo một mẫu thay vì một giá trị chính xác. Mẫu có hai ký tự đại diện: % khớp với một dãy ký tự bất kỳ, kể cả dãy rỗng, còn _ khớp với đúng một ký tự. Vì vậy city LIKE 'Mos%' tìm mọi thành phố bắt đầu bằng “Mos”, model LIKE '%300' tìm các mẫu máy bay kết thúc bằng “300”, và airport_name LIKE '%ovo%' tìm những tên có chứa “ovo” ở bất cứ đâu. Trong PostgreSQL và DuckDB, LIKE phân biệt chữ hoa chữ thường; ILIKE là phiên bản không phân biệt. NOT LIKE đảo ngược phép so khớp, nhưng cả hai đều không bao giờ trả về những dòng mà cột là NULL.
Mọi ví dụ chạy trên cơ sở dữ liệu hãng hàng không của khóa SQL trên Nomto, với cú pháp PostgreSQL trên DuckDB (động cơ của Sân tập SQL trên Nomto), và mọi bảng kết quả đều là kết quả thật. Nếu bạn làm việc với SQL Server hay MySQL, phổ biến ở nhiều ngân hàng và công ty phần mềm tại Việt Nam, các khác biệt quan trọng được ghi chú ở từng phần.
Cú pháp LIKE
SELECT columns
FROM table
WHERE text_column LIKE 'pattern';
Mẫu là một chuỗi bình thường đặt trong dấu nháy đơn. Không có ký tự đại diện, LIKE hoạt động giống =; chính % và _ mới làm nó hữu ích. Mẫu phải khớp với toàn bộ giá trị, nên vị trí của % rất quan trọng:
| mẫu | nghĩa là | khớp với |
|---|---|---|
'Mos%' |
bắt đầu bằng “Mos” | Moscow |
'%sk' |
kết thúc bằng “sk” | Omsk, Tomsk, Irkutsk |
'%ovo%' |
chứa “ovo” | Domodedovo, Pulkovo |
'_____' |
đúng năm ký tự | Kazan, Sochi |
'S_r%' |
S, một ký tự bất kỳ, r, rồi bất cứ gì | Saratov, Surgut |
Ký tự đại diện %: bắt đầu bằng, kết thúc bằng, có chứa
Bắt đầu bằng
SELECT airport_code, airport_name, city
FROM airports
WHERE city LIKE 'Mos%'
ORDER BY airport_code;
| airport_code | airport_name | city |
|---|---|---|
| DME | Domodedovo | Moscow |
| SVO | Sheremetyevo | Moscow |
| VKO | Vnukovo | Moscow |
Kết thúc bằng
SELECT model
FROM aircrafts
WHERE model LIKE '%300'
ORDER BY model;
| model |
|---|
| Boeing 737-300 |
| Boeing 767-300 |
| Boeing 777-300 |
Có chứa
% ở cả hai phía nghĩa là “ở bất cứ đâu trong chuỗi”:
SELECT airport_name, city
FROM airports
WHERE airport_name LIKE '%ovo%'
ORDER BY airport_name
LIMIT 6;
| airport_name | city |
|---|---|
| Domodedovo | Moscow |
| Ivanovo South | Ivanovo |
| Kemerovo | Kemerovo |
| Khomutovo | Yuzhno-Sakhalinsk |
| Khrabrovo | Kaliningrad |
| Koltsovo | Yekaterinburg |
Kết quả đầy đủ có 12 sân bay; ở đây truy vấn được cắt còn sáu dòng.
“Có chứa” thường bắt được nhiều hơn bạn muốn. Tên hành khách được lưu dạng “TÊN HỌ” bằng chữ in hoa. Tìm họ Ivanov bằng '%IVANOV%' trả về 14.040 vé, nhưng '% IVANOV' (một dấu cách, rồi họ ở đúng cuối chuỗi) chỉ trả về 7.539:
SELECT CASE WHEN passenger_name LIKE '% IVANOV' THEN 'IVANOV'
WHEN passenger_name LIKE '% IVANOVA' THEN 'IVANOVA'
ELSE 'other' END AS surname,
COUNT(*) AS tickets
FROM tickets
WHERE passenger_name LIKE '%IVANOV%'
GROUP BY surname
ORDER BY tickets DESC;
| surname | tickets |
|---|---|
| IVANOV | 7539 |
| IVANOVA | 6501 |
Mẫu lỏng lẻo đã âm thầm gom luôn cả dạng giống cái Ivanova. Chuyện tương tự xảy ra với tên Việt: '%LAN%' khớp cả “LAN”, “LANH” lẫn “THANH LAN”. Hãy neo mẫu chặt nhất mà dữ liệu cho phép.
Ký tự đại diện _: đúng một ký tự
_ thay cho một ký tự, không hơn không kém. Năm dấu gạch dưới tìm các thành phố có tên năm chữ cái:
SELECT city
FROM airports
WHERE city LIKE '_____'
ORDER BY city;
| city |
|---|
| Anapa |
| Chita |
| Kazan |
| Kirov |
| Kursk |
| Kyzyl |
| Mirny |
| Nadym |
| Penza |
| Pskov |
| Sochi |
| Tomsk |
| Ukhta |
Kết hợp cả hai ký tự đại diện: 'S_r%' nghĩa là S, một ký tự bất kỳ, r, rồi bất cứ gì.
SELECT city FROM airports WHERE city LIKE 'S_r%' ORDER BY city;
| city |
|---|
| Saransk |
| Saratov |
| Strezhevoy |
| Surgut |
Strezhevoy không phải lỗi: S, rồi “t” cho dấu gạch dưới, rồi “r”. _ khớp với bất kỳ ký tự nào, kể cả phụ âm.
Tìm không phân biệt hoa thường: ILIKE và LOWER
Trong PostgreSQL và DuckDB, LIKE so sánh từng ký tự chính xác, nên chữ thường không khớp với chữ hoa:
SELECT 'ABC' LIKE 'abc' AS like_result, 'ABC' ILIKE 'abc' AS ilike_result;
| like_result | ilike_result |
|---|---|
| false | true |
WHERE city LIKE 'mos%' không trả về dòng nào; WHERE city ILIKE 'mos%' trả về ba sân bay của Moscow. ILIKE không thuộc SQL chuẩn: nó có trong PostgreSQL, DuckDB và Snowflake, nhưng không có trong MySQL hay SQL Server. Cách viết chạy được ở mọi nơi là đưa cả hai phía về chữ thường:
SELECT city FROM airports WHERE lower(city) LIKE 'mos%';
Trong MySQL và SQL Server, việc phân biệt hoa thường phụ thuộc vào collation của cột, và collation mặc định không phân biệt, nên LIKE 'mos%' bình thường đã tìm ra Moscow. Khác biệt này hay gây bất ngờ khi chuyển một truy vấn từ hệ này sang hệ khác.
Tìm chữ tiếng Việt có dấu
Với dữ liệu tiếng Việt, hoa thường chỉ là một nửa vấn đề. Người dùng gõ “nguyen”, còn trong bảng là “Nguyễn”. Với LIKE và ILIKE, “e” và “ễ” là hai ký tự khác nhau:
SELECT name,
name ILIKE '%nguyen%' AS ilike_result,
strip_accents(lower(name)) LIKE '%nguyen%' AS no_accents_result
FROM (VALUES ('Nguyễn Văn Đức'), ('NGUYỄN THỊ HOA'), ('Trần Minh Đức')) t(name);
| name | ilike_result | no_accents_result |
|---|---|---|
| Nguyễn Văn Đức | false | true |
| NGUYỄN THỊ HOA | false | true |
| Trần Minh Đức | false | false |
strip_accents của DuckDB bỏ dấu thanh và dấu mũ, nhưng không đổi “đ” thành “d”: “đ” là một chữ cái riêng, không phải “d” cộng dấu. Phải thay thêm bằng replace:
SELECT strip_accents(lower('Nguyễn Văn Đức')) AS a,
replace(strip_accents(lower('Nguyễn Văn Đức')), 'đ', 'd') AS b;
| a | b |
|---|---|
| nguyen van đuc | nguyen van duc |
Ở các hệ khác:
- SQL Server: dùng collation không phân biệt dấu, ví dụ
WHERE name COLLATE Vietnamese_CI_AI LIKE N'%nguyen%'. Nhớ tiền tốNcho chuỗi Unicode, nếu không dấu tiếng Việt có thể bị biến thành dấu hỏi. - MySQL 8: collation mặc định
utf8mb4_0900_ai_ciđã không phân biệt hoa thường lẫn dấu (ai= accent-insensitive), nên “nguyen” khớp “Nguyễn”. - PostgreSQL: dùng extension
unaccent,unaccent(lower(name)) LIKE '%nguyen%', và cũng nên kiểm tra riêng chữ “đ”.
Nếu bảng lớn và phải tìm kiểu này thường xuyên, hãy lưu sẵn một cột không dấu (ví dụ name_search) và đánh index trên cột đó.
NOT LIKE, và vì sao các dòng NULL biến mất
NOT LIKE giữ lại những dòng không khớp. Cái bẫy: nếu cột là NULL, cả LIKE lẫn NOT LIKE đều trả về NULL, và WHERE loại dòng đó trong cả hai trường hợp. Khoảng một nửa số vé có email trong contact_data; chia chúng theo việc địa chỉ có chứa dấu gạch dưới hay không (\_ hoạt động thế nào được giải thích ở phần tiếp theo):
SELECT COUNT(*) FILTER (WHERE contact_data->>'email' LIKE '%\_%' ESCAPE '\') AS with_underscore,
COUNT(*) FILTER (WHERE contact_data->>'email' NOT LIKE '%\_%' ESCAPE '\') AS without_underscore,
COUNT(*) FILTER (WHERE contact_data->>'email' IS NULL) AS no_email,
COUNT(*) AS total
FROM tickets;
| with_underscore | without_underscore | no_email | total |
|---|---|---|---|
| 87828 | 111255 | 167650 | 366733 |
LIKE cộng NOT LIKE được 199.083, không phải 366.733: 167.650 vé không có email không nằm trong nhóm nào. Nếu “không khớp” cần bao gồm cả “không có giá trị”, hãy viết rõ ra: WHERE email NOT LIKE '%\_%' ESCAPE '\' OR email IS NULL, hoặc WHERE COALESCE(email, '') NOT LIKE .... Bài NULL trong SQL giải thích logic ba giá trị đứng sau chuyện này.
Thoát ký tự: tìm chính ký tự % hoặc _
Vì % và _ là ký tự đại diện, bạn cần một ký tự thoát (escape) để tìm chính chúng. Không có nó, “email chứa dấu gạch dưới” khớp với mọi email:
SELECT COUNT(*) AS emails,
COUNT(*) FILTER (WHERE email LIKE '%_%') AS unescaped,
COUNT(*) FILTER (WHERE email LIKE '%\_%' ESCAPE '\') AS escaped
FROM (SELECT contact_data->>'email' AS email FROM tickets) t
WHERE email IS NOT NULL;
| emails | unescaped | escaped |
|---|---|---|
| 199083 | 199083 | 87828 |
'%_%' nghĩa là “ít nhất một ký tự bất kỳ”, chuỗi không rỗng nào cũng thỏa. Với ESCAPE '\', cặp \_ nghĩa là một dấu gạch dưới thật.
Ký tự thoát mặc định khác nhau giữa các hệ, và đây là chỗ truy vấn hỏng khi bạn sao chép chúng:
- PostgreSQL mặc định coi dấu gạch chéo ngược là ký tự thoát, nên
LIKE '%\_%'chạy được mà không cầnESCAPE. - DuckDB không có ký tự thoát mặc định. Cùng
LIKE '%\_%'không kèmESCAPEtrả về 0 dòng trên dữ liệu này, vì nó đi tìm một dấu gạch chéo ngược thật. Luôn viếtESCAPE '\'. - MySQL mặc định dùng dấu gạch chéo ngược, giống PostgreSQL.
- SQL Server cũng không có mặc định, nhưng cho phép đặt ký tự đại diện trong ngoặc vuông:
LIKE '%[_]%'. Ở các hệ khác, ngoặc vuông là ký tự bình thường (DuckDB trả về 0 dòng với mẫu đó).
Mệnh đề ESCAPE viết rõ chạy được ở tất cả, và bạn chọn ký tự nào cũng được, ví dụ LIKE '%!_%' ESCAPE '!'.
Nhiều mẫu cùng lúc
LIKE nhận một mẫu. Muốn nhiều mẫu, hãy nối điều kiện bằng OR và bọc chúng trong ngoặc đơn nếu WHERE còn điều kiện khác:
SELECT city
FROM airports
WHERE (city LIKE 'Nov%' OR city LIKE 'Nor%')
ORDER BY city;
| city |
|---|
| Norilsk |
| Novokuznetsk |
| Novosibirsk |
| Novy Urengoy |
Không có ngoặc, WHERE a AND b OR c được hiểu là (a AND b) OR c, một nguồn kinh điển của dòng thừa.
PostgreSQL còn chấp nhận city LIKE ANY (ARRAY['Nov%', 'Nor%']). DuckDB thì không: nó báo lỗi Unsupported comparison "~~" for ANY/ALL subquery, nên trong Sân tập SQL trên Nomto hãy dùng OR.
Khi mẫu phức tạp hơn, hãy chuyển sang biểu thức chính quy (regex). Trong DuckDB, regexp_matches(city, '^(Nov|Nor|Noy)') trả về Norilsk, Novokuznetsk, Novosibirsk, Novy Urengoy và Noyabrsk. PostgreSQL dùng toán tử city ~ '^(Nov|Nor|Noy)'. Cẩn thận với ~ trong DuckDB: ở đó regex phải khớp với toàn bộ chuỗi, nên city ~ '^Nov' không trả về gì và bạn phải viết city ~ 'Nov.*'.
LIKE so với =
= |
LIKE |
|
|---|---|---|
| Khớp với | giá trị chính xác | mẫu có % và _ |
| Ký tự đại diện | không có (% là ký tự thường) |
%, _ |
| Tốc độ | luôn dùng được index | chỉ dùng được index khi có tiền tố cố định |
| Dùng điển hình | status = 'Arrived' |
city LIKE 'Nov%' |
Hãy dùng = mỗi khi bạn biết đầy đủ giá trị. LIKE không có ký tự đại diện cho kết quả như = trong PostgreSQL và DuckDB ('abc ' LIKE 'abc' và 'abc ' = 'abc' đều sai). Trong SQL Server, = bỏ qua dấu cách ở cuối còn LIKE thì không, nên ở đó 'abc ' = 'abc' đúng trong khi 'abc' LIKE 'abc ' sai.
LIKE cũng chạy được trên ngày tháng đã đổi sang chuỗi, CAST(scheduled_departure AS VARCHAR) LIKE '2026-08-01%', và ở đây nó cho đúng 568 chuyến bay như một khoảng thời gian. Hãy ưu tiên khoảng thời gian, scheduled_departure >= '2026-08-01' AND scheduled_departure < '2026-08-02': nó giữ nguyên kiểu của cột, chạy được ở mọi hệ bất kể định dạng chuỗi của ngày, và dùng được index. Xem thêm trong bài hàm xử lý ngày tháng trong SQL.
Hiệu năng của LIKE
Trên bảng lớn, cách mẫu bắt đầu quyết định nó chạy nhanh hay chậm:
- Tiền tố cố định (
'Mos%') dùng được index B-tree, vì mọi giá trị khớp nằm cạnh nhau theo thứ tự sắp xếp. Trong PostgreSQL, điều này chỉ có sẵn với collationC; với collation khác bạn cần index tạo bằngtext_pattern_ops(hoặcvarchar_pattern_ops). - Ký tự đại diện ở đầu (
'%ovo'hoặc'%ovo%') không dùng được index thường: cơ sở dữ liệu phải đọc mọi giá trị. Trong PostgreSQL, index trigram (extensionpg_trgmvới index GIN hoặc GiST) làm cho tìm kiếm “có chứa” chạy nhanh. lower(col) LIKE ...vàILIKEkhông dùng index thường trêncol. Trong PostgreSQL, hãy tạo index trênlower(col)cho trường hợp đầu;ILIKEcần index trigram.
Các engine phân tích dạng cột như DuckDB, BigQuery hay Snowflake vốn quét cột rất nhanh, nên với dữ liệu cỡ khóa học bạn sẽ không thấy khác biệt. Trên một bảng PostgreSQL, MySQL hay SQL Server thật với hàng triệu dòng, bạn sẽ thấy.
Lỗi thường gặp
- Quên ký tự đại diện.
WHERE city LIKE 'Mos'chỉ tìm thành phố tên đúng là “Mos”. - Dùng nháy kép cho mẫu. Trong PostgreSQL và DuckDB,
"Mos%"là tên cột, không phải chuỗi. Hãy dùng nháy đơn. %ở cả hai phía trong khi bạn chỉ cần tiền tố. Chậm hơn và khớp quá nhiều, như ví dụ Ivanov/Ivanova._không được thoát trong mẫu.LIKE 'view_%'khớp “view_offer”, nhưng cũng khớp “viewX” hay “views”; hãy thoát nó nếu bạn cần dấu gạch dưới thật.- Chờ
NOT LIKEtrả về dòng NULL. Nó không bao giờ làm vậy; hãy thêmOR col IS NULL. - Tìm “nguyen” trong cột có dấu mà không bỏ dấu hay dùng collation không phân biệt dấu.
- Trộn
ANDvàORkhông có ngoặc khi kết hợp nhiều mẫu.
Để cắt lấy một phần chuỗi sau khi đã tìm ra (SPLIT_PART, SUBSTRING, REPLACE), xem bài hàm chuỗi trong SQL. Lọc theo mẫu cũng là một chủ đề trong SQL cheat sheet.
Câu hỏi thường gặp
Dấu % trong LIKE của SQL nghĩa là gì?
% khớp với một dãy ký tự bất kỳ, kể cả dãy rỗng. 'Mos%' khớp “Mos”, “Moscow” và mọi chuỗi bắt đầu bằng “Mos”. _ khớp với đúng một ký tự.
LIKE trong SQL có phân biệt chữ hoa chữ thường không?
Trong PostgreSQL, DuckDB và Oracle thì có. Trong MySQL và SQL Server, nó phụ thuộc collation, và collation mặc định không phân biệt. Để so khớp không phân biệt hoa thường trong PostgreSQL hoặc DuckDB, dùng ILIKE, hoặc lower(col) LIKE 'mẫu' ở bất kỳ hệ nào.
Làm sao tìm “nguyen” mà khớp được “Nguyễn”?
Bỏ dấu trước khi so khớp: strip_accents(lower(col)) trong DuckDB, unaccent(lower(col)) trong PostgreSQL, rồi thay riêng “đ” thành “d”. SQL Server dùng collation Vietnamese_CI_AI; MySQL 8 với collation mặc định đã không phân biệt dấu.
Làm sao thoát dấu gạch dưới hoặc dấu phần trăm trong LIKE?
Đặt một ký tự thoát trước nó và khai báo bằng ESCAPE: col LIKE '%\_%' ESCAPE '\' tìm các giá trị có dấu gạch dưới thật. PostgreSQL và MySQL mặc định dùng dấu gạch chéo ngược; DuckDB và SQL Server thì không, nên mệnh đề ESCAPE viết rõ là cách chạy được ở mọi nơi. SQL Server cũng chấp nhận '%[_]%'.
Có dùng LIKE với nhiều giá trị được không?
Không trực tiếp. Hãy nối điều kiện bằng OR (city LIKE 'Nov%' OR city LIKE 'Nor%'), dùng LIKE ANY (ARRAY[...]) trong PostgreSQL, hoặc chuyển sang biểu thức chính quy.
LIKE có chậm hơn = không?
LIKE với tiền tố cố định dùng được index và nhanh gần bằng =. Mẫu bắt đầu bằng % phải kiểm tra từng dòng, chậm hơn nhiều trên bảng lớn, trừ khi bạn thêm index trigram (PostgreSQL) hoặc index full-text.
LIKE có dùng được với số không?
Trong PostgreSQL bạn phải ép kiểu trước: CAST(col AS TEXT) LIKE '12%'. MySQL tự chuyển đổi. Với số, điều kiện khoảng (col BETWEEN 1200 AND 1299) gần như luôn là lựa chọn tốt hơn.
Ngôn ngữ khác: English, Español, Português (Brasil), 简体中文