Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

LIKE và Regex: pattern matching mạnh cho BA

SQL and Data Analysis cho BA Bài 8/60

Mở đầu — vì sao bài này quan trọng

Là một BA (Business Analyst), bạn sẽ rất nhanh nhận ra rằng dữ liệu thực tế trong doanh nghiệp hiếm khi "sạch sẽ" và "đẹp đẽ" như trong sách giáo khoa. Số điện thoại khách hàng được nhập đủ kiểu: 0901234567, +84901234567, 090-123-4567, hay thậm chí 84 901 234 567. Email thì có cái viết hoa, cái viết thường, cái gõ nhầm @gmial.com. Tên sản phẩm trên sàn Shopee có khi dài lê thê kèm cả emoji và mã giảm giá. Khi sếp hỏi bạn "Có bao nhiêu khách hàng đăng ký bằng email Gmail?", "Bao nhiêu đơn hàng có mã khuyến mãi bắt đầu bằng TET2026?", hay "Tìm cho anh tất cả sản phẩm có chữ 'chính hãng' trong tên", bạn sẽ không thể dùng dấu = đơn thuần được.

Đây chính là lúc pattern matching — so khớp theo mẫu — trở thành vũ khí của bạn. Trong SQL, hai công cụ chính cho việc này là LIKE (đơn giản, có mặt ở mọi hệ quản trị) và Regular Expression (regex — mạnh mẽ hơn nhiều, dùng khi LIKE "bó tay"). Nắm vững hai công cụ này, bạn sẽ tự lọc, tự kiểm tra, tự làm sạch dữ liệu mà không cần phải xuất ra Excel rồi ngồi lọc tay hàng giờ. Đây là một trong những kỹ năng tạo ra khác biệt rõ rệt giữa một BA "biết viết SELECT" và một BA "thực sự khai thác được dữ liệu".

Khái niệm cốt lõi

LIKE và hai ký tự đại diện

LIKE là toán tử so khớp theo mẫu cơ bản nhất. Nó chỉ có đúng hai ký tự đặc biệt (wildcard) bạn cần nhớ:

  • % — đại diện cho 0 hoặc nhiều ký tự bất kỳ.
  • _ (dấu gạch dưới) — đại diện cho đúng 1 ký tự bất kỳ.
Hãy xem vài mẫu cơ bản:

WHERE email LIKE '%@gmail.com'      -- kết thúc bằng @gmail.com
WHERE email LIKE 'admin%'           -- bắt đầu bằng admin
WHERE ten_sp LIKE '%chính hãng%'    -- chứa cụm "chính hãng" ở bất kỳ đâu
WHERE ma_kh LIKE 'KH____'           -- KH + đúng 4 ký tự bất kỳ (ví dụ KH0042)
WHERE sdt LIKE '09_1%'              -- bắt đầu 09, ký tự thứ 3 bất kỳ, rồi 1

Ghi nhớ quy tắc vàng: %đầu mẫu nghĩa là "không quan tâm phía trước có gì", %cuối nghĩa là "không quan tâm phía sau có gì". %abc% là "chứa abc ở bất kỳ vị trí nào".

NOT LIKE và phủ định

Khi cần lọc ngược lại, dùng NOT LIKE:

WHERE email NOT LIKE '%@gmail.com'  -- email KHÔNG phải Gmail

Phân biệt chữ hoa/thường

Đây là cái bẫy lớn nhất và khác nhau giữa các hệ quản trị:

  • MySQL: với collation mặc định (kiểu utf8mb4_general_ci — chữ ci nghĩa là case-insensitive), LIKE không phân biệt hoa thường. 'ABC' LIKE 'abc' trả về true.
  • PostgreSQL: LIKE phân biệt hoa thường. Muốn không phân biệt, dùng ILIKE (chữ I = insensitive).
-- PostgreSQL: tìm không phân biệt hoa thường
WHERE ten_kh ILIKE '%nguyen%'   -- khớp cả Nguyen, NGUYEN, nguyen

Một mẹo an toàn dùng được ở mọi nơi: ép cả hai vế về cùng một dạng với LOWER():

WHERE LOWER(email) LIKE LOWER('%@Gmail.com%')

Khi cần tìm chính ký tự % hoặc _

Nếu dữ liệu của bạn chứa chính ký tự phần trăm (ví dụ "Giảm 50%"), bạn phải "thoát" (escape) nó:

WHERE khuyen_mai LIKE '%50\%%'              -- tìm chuỗi chứa "50%"
WHERE khuyen_mai LIKE '%50!%%' ESCAPE '!'   -- tự định nghĩa ký tự escape

Regex — khi LIKE không đủ sức

LIKE rất tiện nhưng có giới hạn: nó không hiểu "một dãy chữ số bất kỳ", "hoặc cái này hoặc cái kia", hay "lặp lại 3 đến 5 lần". Regular Expression giải quyết tất cả những thứ đó. Cú pháp gọi regex khác nhau theo hệ:

  • MySQL (từ 8.0): REGEXP_LIKE(cot, 'mẫu') hoặc toán tử col REGEXP 'mẫu'.
  • PostgreSQL: toán tử ~ (phân biệt hoa thường) và ~ (không phân biệt). Phủ định là !~!~.
Các thành phần regex quan trọng nhất cho BA:

Ký hiệuÝ nghĩa
^bắt đầu chuỗi
$kết thúc chuỗi
.một ký tự bất kỳ
[0-9] hoặc [[:digit:]]một chữ số
[a-z]một chữ cái thường
+lặp 1 lần trở lên
*lặp 0 lần trở lên
{3} / {3,5}lặp đúng 3 lần / từ 3 đến 5 lần
\"hoặc" (OR)
()nhóm
Ví dụ kinh điển — kiểm tra một số điện thoại di động Việt Nam hợp lệ (bắt đầu bằng 0, theo sau là 9 chữ số):

-- PostgreSQL
WHERE sdt ~ '^0[0-9]{9}$'

-- MySQL WHERE REGEXP_LIKE(sdt, '^0[0-9]{9}$')

Đây là điều LIKE không làm nổi: LIKE '0%' chỉ kiểm tra "bắt đầu bằng 0", không kiểm tra được "theo sau đúng 9 chữ số".

Tình huống thực tế

Tình huống 1: Tiki làm sạch tệp email để gửi chiến dịch

Đội Marketing của một sàn TMĐT kiểu Tiki chuẩn bị gửi email khuyến mãi 6/6 cho 2 triệu khách hàng. Trước khi đẩy danh sách vào công cụ gửi mail, BA được giao nhiệm vụ "lọc ra những email trông không hợp lệ để khỏi tốn chi phí và tránh bị đánh dấu spam". Trong bảng customers có cột email.

Nếu chỉ dùng LIKE, bạn chỉ kiểm tra được vài điều thô sơ như "có dấu @ không". Nhưng với regex, BA viết được một bộ lọc chặt chẽ:

-- Đếm số email KHÔNG đúng định dạng cơ bản
SELECT COUNT(*) AS email_loi
FROM customers
WHERE email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';

Kết quả trả về 18.400 email lỗi. Đào sâu hơn, BA tách riêng nhóm gõ nhầm tên miền phổ biến:

SELECT email
FROM customers
WHERE email ~* '@(gmial|gmai|gamil|hotmial)\.com$';

Hóa ra có hơn 3.000 khách gõ nhầm @gmial.com thay vì @gmail.com. Thay vì vứt bỏ, đội Marketing quyết định tự sửa các tên miền gõ nhầm phổ biến này và cứu lại được khoảng 3.000 địa chỉ liên hệ giá trị.

Bài học rút ra: Regex cho phép BA định nghĩa "thế nào là dữ liệu hợp lệ" một cách chính xác. Một bộ lọc tốt không chỉ loại rác mà còn giúp phát hiện vấn đề có thể sửa được, từ đó tạo giá trị kinh doanh thực.

Tình huống 2: Grab/Be chuẩn hóa số điện thoại tài xế

Một nền tảng gọi xe kiểu Be có bảng drivers với cột phone. Do dữ liệu được nhập từ nhiều nguồn (app, tổng đài, đối tác), số điện thoại lưu đủ định dạng: 0901234567, +84901234567, 84901234567, 090 123 4567. BA cần biết có bao nhiêu tài xế thực sự có số hợp lệ và bao nhiêu cần chuẩn hóa lại.

Trước tiên, đếm nhanh các định dạng bằng LIKE:

SELECT
  COUNT(*) FILTER (WHERE phone LIKE '0%')   AS bat_dau_0,
  COUNT(*) FILTER (WHERE phone LIKE '+84%') AS bat_dau_plus84,
  COUNT(*) FILTER (WHERE phone LIKE '84%')  AS bat_dau_84
FROM drivers;

Nhưng LIKE không phân biệt được "090 123 4567" (có khoảng trắng) với số liền mạch. BA chuyển sang regex để bắt đúng những số "bẩn":

-- Tìm các số chứa ký tự không phải chữ số (khoảng trắng, dấu gạch, dấu +)
SELECT phone
FROM drivers
WHERE phone ~ '[^0-9]';

Kết quả: 7.200 số cần làm sạch. BA lập báo cáo gửi đội kỹ thuật, đề xuất một quy tắc chuẩn hóa: bỏ mọi ký tự không phải số, đổi đầu 84/+84 về 0. Nhờ đó, hệ thống SMS xác thực giảm tỷ lệ gửi thất bại từ 4,1% xuống còn 0,6% trong tháng sau.

Bài học rút ra: LIKE rất nhanh để "ước lượng quy mô" theo nhóm định dạng, còn regex dùng để "soi chính xác" những bản ghi vi phạm quy tắc. Một BA giỏi biết dùng cả hai đúng chỗ.

Tình huống 3: Phân loại mã khuyến mãi trong dịp Tết

Sàn TMĐT chạy hàng trăm chương trình khuyến mãi. Bảng orders có cột voucher_code. Quy ước nội bộ: mã Tết bắt đầu bằng TET, theo sau là năm 4 chữ số, rồi một ký tự loại (F = freeship, D = giảm tiền), ví dụ TET2026F, TET2026D. BA cần báo cáo doanh thu theo từng loại mã Tết 2026.

SELECT
  CASE
    WHEN voucher_code ~ '^TET2026F$' THEN 'Freeship Tết'
    WHEN voucher_code ~ '^TET2026D$' THEN 'Giảm tiền Tết'
    WHEN voucher_code ~ '^TET2026'   THEN 'Tết khác'
    ELSE 'Không phải mã Tết'
  END AS loai_ma,
  COUNT(*)        AS so_don,
  SUM(gia_tri)    AS doanh_thu
FROM orders
WHERE voucher_code IS NOT NULL
GROUP BY 1
ORDER BY doanh_thu DESC;

Báo cáo cho thấy mã TET2026F (freeship) tạo ra 62% số đơn dùng voucher Tết, nhưng mã TET2026D (giảm tiền) lại đem về doanh thu trung bình mỗi đơn cao hơn 35%. Đây là insight giúp đội Growth quyết định phân bổ ngân sách khuyến mãi đợt tới.

Bài học rút ra: Pattern matching không chỉ để lọc, mà còn để phân loại — kết hợp regex với CASE WHEN biến một cột text lộn xộn thành chiều phân tích (dimension) gọn gàng cho báo cáo.

Hướng dẫn từng bước

Khi gặp một yêu cầu so khớp theo mẫu, hãy đi theo quy trình sau:

  • Mô tả mẫu bằng lời trước khi viết SQL. Ví dụ: "số điện thoại = bắt đầu bằng 0, theo sau đúng 9 chữ số". Diễn đạt rõ bằng tiếng Việt giúp bạn chọn đúng công cụ.
  • Hỏi: LIKE có đủ không? Nếu mẫu chỉ là "bắt đầu bằng", "kết thúc bằng", "chứa", hoặc "đúng N ký tự" — dùng LIKE cho đơn giản và dễ đọc. Đừng dùng regex nếu không cần.
  • Nếu cần logic phức tạp (chữ số liên tiếp, "hoặc", lặp có số lượng, kiểm tra định dạng) — chuyển sang regex.
  • Xác định hệ quản trị và cú pháp. MySQL dùng REGEXP_LIKE/REGEXP; PostgreSQL dùng ~, ~*, !~. Kiểm tra cả vấn đề hoa/thường.
  • Test trên tập nhỏ trước. Chạy SELECT cot FROM bang WHERE <điều kiện> LIMIT 20; để mắt thường xác nhận kết quả khớp đúng kỳ vọng, trước khi dùng nó trong báo cáo lớn hay câu lệnh xóa/cập nhật.
  • Đếm và đối chiếu. Chạy COUNT(*) cho cả nhánh khớp và nhánh không khớp, cộng lại xem có bằng tổng bản ghi không. Đây là cách nhanh để phát hiện mẫu bị thiếu trường hợp.

Lỗi thường gặp & mẹo

Quên rằng PostgreSQL phân biệt hoa thường. Đây là lỗi số một khi BA quen MySQL chuyển sang Postgres. Tìm LIKE '%nguyen%' sẽ bỏ sót Nguyen. Dùng ILIKE hoặc ~*.

Dùng LIKE với cột số. WHERE so_dien_thoai LIKE '09%' chỉ hoạt động nếu cột là kiểu text. Nếu là kiểu số (INT/BIGINT), một số hệ sẽ ép kiểu ngầm gây chậm hoặc sai. Hãy CAST(cot AS TEXT) cho rõ ràng, hoặc tốt hơn là lưu số điện thoại dưới dạng text ngay từ đầu (vì có số 0 ở đầu).

LIKE '%abc%' làm chậm truy vấn. Khi % nằm ở đầu mẫu, database không dùng được index thông thường, buộc phải quét toàn bảng. Trên bảng nhiều triệu dòng, điều này rất tốn thời gian. Mẹo: nếu chỉ cần "bắt đầu bằng" (LIKE 'abc%', không có % ở đầu), index vẫn dùng được và nhanh hơn nhiều. Với nhu cầu tìm trong văn bản dài thường xuyên, hãy đề xuất đội kỹ thuật cân nhắc full-text search.

Quên escape ký tự %_. Tìm khách có ghi chú chứa "giảm 50%" mà viết LIKE '%50%%' sẽ ra kết quả sai vì % được hiểu là wildcard. Dùng ESCAPE.

Nhầm . trong regex là dấu chấm thật. Trong regex, . nghĩa là "ký tự bất kỳ". Để khớp dấu chấm thật (như trong .com), phải escape thành \.. Mẫu @gmail.com viết regex đúng là @gmail\.com.

Mẹo gộp nhiều điều kiện với IN-style regex. Thay vì viết ba dòng OR LIKE, regex gộp gọn bằng |:

-- Thay vì: email LIKE '%@gmail.com' OR email LIKE '%@yahoo.com' OR ...
WHERE email ~* '@(gmail|yahoo|outlook)\.com$'

Mẹo dùng lớp ký tự POSIX cho dễ đọc. [[:digit:]] thay cho [0-9], [[:alpha:]] thay cho [a-zA-Z] — hoạt động ở cả MySQL và PostgreSQL, rõ nghĩa hơn.

Bài tập thực hành

Giả sử bạn có bảng customers(id, full_name, email, phone, address) của một sàn TMĐT Việt Nam. Hãy viết truy vấn cho các yêu cầu sau (thử trên cả MySQL và PostgreSQL nếu có thể):

  • Lọc tất cả khách hàng dùng email Gmail, không phân biệt hoa thường.
  • Đếm số khách hàng có số điện thoại không đúng định dạng "0 + 9 chữ số" (dùng regex).
  • Tìm những khách có địa chỉ chứa từ "Quận 1" hoặc "Quận 3" tại TP.HCM (gợi ý: dùng | trong regex).
  • Tìm các email gõ nhầm tên miền: kết thúc bằng @gmial.com hoặc @gmai.com.
  • Phân loại số điện thoại theo nhà mạng dựa trên đầu số: Viettel (03, 09x theo quy ước của bạn), Mobifone, Vinaphone — dùng CASE WHEN kết hợp regex, trả về số lượng mỗi nhóm.
  • Tìm những full_name chứa ký tự đặc biệt bất thường (không phải chữ cái, khoảng trắng hay dấu tiếng Việt) — đây có thể là dữ liệu bẩn cần làm sạch.
Với mỗi câu, hãy chạy thêm một COUNT(*) để biết quy mô và tự hỏi: "Con số này có hợp lý so với tổng số khách không?"

Tóm tắt

Pattern matching là kỹ năng sống còn của BA khi làm việc với dữ liệu thực — vốn luôn lộn xộn, đa định dạng. Hãy ghi nhớ những điểm cốt lõi:

  • LIKE dùng cho các mẫu đơn giản với hai wildcard: % (nhiều ký tự) và _ (đúng một ký tự). Nhanh, dễ đọc, nên ưu tiên khi đủ dùng.
  • Cẩn thận với hoa/thường: MySQL thường không phân biệt, PostgreSQL có phân biệt — dùng ILIKE/~* hoặc LOWER() để an toàn.
  • Regex (REGEXP_LIKE trong MySQL, ~/~* trong PostgreSQL) mở ra sức mạnh thật sự: kiểm tra định dạng, đếm chữ số, logic "hoặc", lặp có số lượng — những thứ LIKE không làm được.
  • Kết hợp pattern matching với CASE WHEN để biến cột text lộn xộn thành chiều phân tích sạch đẹp cho báo cáo.
  • Luôn test trên tập nhỏ, đếm đối chiếu, và lưu ý %abc% có thể làm chậm truy vấn lớn.
Khi bạn thành thạo LIKE và regex, bạn không còn phụ thuộc vào việc xuất dữ liệu ra Excel để lọc tay nữa. Bạn tự kiểm tra chất lượng dữ liệu, tự phân loại, tự trả lời những câu hỏi "tìm cho anh tất cả những cái có chứa..." ngay trong SQL — nhanh, chính xác và lặp lại được. Đó chính là dấu hiệu của một BA thực sự làm chủ dữ liệu.