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

Bảng giờ khởi hành với chiếc kính lúp làm nổi bật mọi tên thành phố bắt đầu bằng cùng vài chữ cái

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:

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:

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:

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

Để 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), 简体中文