Menu
ESC

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

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

Đang tải...

Bài 41 — SQL cho BA: SELECT, WHERE, JOIN cơ bản

Chương Trình Chứng Chỉ Business Analyst Hoàn Chỉnh Bài 41/60

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

Hãy hình dung một tình huống quen thuộc: bạn là BA của một dự án ví điện tử. Trong buổi họp, Product Owner khẳng định "phần lớn user của mình kích hoạt tài khoản trong vòng 24 giờ sau khi đăng ký, nên flow onboarding không có vấn đề gì". Cả phòng gật gù. Nhưng bạn linh cảm điều đó không đúng. Vấn đề là: để kiểm chứng, bạn phải gửi yêu cầu cho team Data, chờ hai ngày, rồi nhận lại một file Excel mà bạn không chắc người ta query đúng ý mình.

Đây chính là khoảnh khắc SQL thay đổi cuộc chơi cho một BA. SQL (Structured Query Language) là ngôn ngữ để "hỏi chuyện" cơ sở dữ liệu. Khi bạn tự viết được một câu query, bạn không còn là người ngồi chờ — bạn là người trực tiếp validate giả thuyết, phát hiện vấn đề, và quan trọng nhất, bảo vệ requirement của mình bằng số liệu thật thay vì cảm tính.

Trong nghề BA hiện đại, SQL không còn là "kỹ năng cộng thêm". Nó là kỹ năng nền. Một BA biết SQL sẽ tự trả lời được những câu hỏi như "có bao nhiêu khách hàng thuộc nhóm này?", "transaction lỗi tập trung ở loại giao dịch nào?", "dữ liệu thực tế có khớp với business rule mình viết không?". Bài này tập trung vào bộ ba nền tảng nhất — SELECT, WHERE, JOIN — đủ để bạn bắt đầu tự query trong công việc thật. Các kỹ thuật nâng cao như GROUP BY, aggregate và window function sẽ là chủ đề của bài kế tiếp.

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

Trước khi viết câu lệnh đầu tiên, bạn cần hiểu dữ liệu được tổ chức thế nào. Mọi cơ sở dữ liệu quan hệ (relational database) đều lưu dữ liệu trong bảng (table) — giống như các sheet Excel. Mỗi bảng có cột (column) mô tả một thuộc tính (ví dụ: customer_id, full_name, created_at) và dòng (row) là một bản ghi cụ thể (một khách hàng, một giao dịch).

SELECT — chọn dữ liệu bạn muốn xem

SELECT là động từ cơ bản nhất: nó nói với database "hãy lấy cho tôi những cột này, từ bảng kia".

SELECT full_name, email, created_at
FROM customers;

Câu này dịch ra tiếng Việt là: "Lấy ba cột full_name, email, created_at từ bảng customers, trả về tất cả các dòng." Nếu muốn lấy mọi cột, bạn dùng dấu sao:

SELECT * FROM customers;

Một mẹo thực dụng cho BA: khi mới khám phá một bảng lạ, hãy thêm LIMIT để chỉ xem vài dòng đầu, tránh kéo về hàng triệu bản ghi làm treo công cụ:

SELECT * FROM transactions LIMIT 10;

WHERE — lọc đúng dòng bạn cần

SELECT trả về tất cả các dòng, nhưng đời thực bạn hiếm khi cần tất cả. WHERE là bộ lọc — nó là nơi business rule của bạn gặp dữ liệu.

SELECT full_name, created_at
FROM customers
WHERE status = 'active';

Bạn có thể kết hợp nhiều điều kiện bằng AND (cả hai phải đúng) và OR (chỉ cần một đúng):

SELECT *
FROM transactions
WHERE amount > 1000000
  AND status = 'failed';

Một số toán tử WHERE mà BA dùng hằng ngày:

  • So sánh: =, <> (khác), >, <, >=, <=
  • BETWEEN ... AND ... — nằm trong khoảng (rất hợp cho ngày tháng và số tiền)
  • IN (...) — thuộc một danh sách (ví dụ WHERE province IN ('Hà Nội', 'TP HCM', 'Đà Nẵng'))
  • LIKE — khớp mẫu chuỗi, với % đại diện cho "bất kỳ ký tự nào" (ví dụ WHERE email LIKE '%@gmail.com')
  • IS NULL / IS NOT NULL — kiểm tra ô trống. Đây là điểm cực kỳ quan trọng: trong SQL, NULL nghĩa là "không có giá trị", và bạn không thể dùng = NULL, bắt buộc phải dùng IS NULL.

JOIN — ghép dữ liệu từ nhiều bảng

Đây là phần biến một BA "biết chút SQL" thành một BA "thực sự dùng được SQL". Dữ liệu thật gần như không bao giờ nằm gọn trong một bảng. Thông tin khách hàng nằm ở bảng customers, giao dịch của họ nằm ở bảng transactions. Hai bảng nối với nhau qua một cột chung — thường là customer_id. JOIN là cách bạn ghép chúng lại.

SELECT c.full_name, t.amount, t.created_at
FROM customers c
JOIN transactions t ON c.customer_id = t.customer_id
WHERE t.amount > 5000000;

Lưu ý chữ ct — đó là alias (tên gọi tắt) giúp câu query gọn và rõ. c.full_name nghĩa là "cột full_name của bảng customers".

Có bốn loại JOIN bạn cần phân biệt, và sự khác nhau giữa chúng có thể quyết định đúng/sai của một con số trong report:

  • INNER JOIN (mặc định khi viết JOIN): chỉ giữ những dòng khớp ở cả hai bảng. Nếu một khách hàng chưa có giao dịch nào, họ sẽ không xuất hiện.
  • LEFT JOIN: giữ tất cả dòng của bảng bên trái, kể cả không khớp. Khách hàng chưa giao dịch vẫn hiện ra, với cột giao dịch để trống (NULL). Đây là JOIN bạn cần khi muốn tìm "ai chưa làm gì".
  • RIGHT JOIN: ngược lại của LEFT, giữ tất cả bảng bên phải. Ít dùng hơn.
  • FULL OUTER JOIN: giữ tất cả của cả hai bên.
Hiểu sai INNER và LEFT JOIN là một trong những nguyên nhân hàng đầu khiến số liệu trong báo cáo bị lệch.

Tình huống thực tế

Tình huống 1 — Ví điện tử Tititada: bóc trần giả định onboarding

Quay lại câu chuyện đầu bài. Bạn là BA tại một startup fintech (gọi là Tititada). PO khẳng định "phần lớn user kích hoạt trong 24 giờ". Bạn có hai bảng: users (chứa user_id, registered_at) và kyc_records (chứa user_id, verified_at, nơi verified_at chỉ có giá trị khi user đã hoàn thành định danh).

Bạn viết:

SELECT COUNT(*) AS chua_kich_hoat
FROM users u
LEFT JOIN kyc_records k ON u.user_id = k.user_id
WHERE k.verified_at IS NULL;

Cái hay nằm ở LEFT JOIN cộng với IS NULL: bạn đang hỏi "có bao nhiêu user trong bảng users mà KHÔNG có bản ghi KYC nào khớp". Kết quả trả về: 4.200 trên tổng 10.000 user — tức 42% chưa bao giờ hoàn thành định danh, chứ chưa nói đến chuyện trong 24 giờ.

Bài học: Nếu bạn dùng INNER JOIN ở đây, 4.200 user "im lặng" đó sẽ biến mất khỏi kết quả, và bạn sẽ vô tình củng cố cho giả định sai của PO. LEFT JOIN + IS NULL là combo kinh điển để tìm ra "những người bị bỏ sót". Con số 42% này lập tức biến cuộc tranh luận cảm tính thành một requirement cụ thể: cần thiết kế lại flow KYC.

Tình huống 2 — Sàn TMĐT: bảo vệ một business rule khi viết requirement

Bạn làm BA cho một sàn thương mại điện tử. Team đang viết requirement cho tính năng "Khách hàng VIP được miễn phí ship cho đơn trên 300.000đ". PO định nghĩa VIP là "khách có tổng chi tiêu cao". Nhưng "cao" là bao nhiêu? Trước khi chốt ngưỡng, bạn cần biết phân bố thực tế.

Bạn có bảng orders (order_id, customer_id, total_amount, order_date). Để xem nhanh các đơn giá trị lớn trong quý gần nhất:

SELECT customer_id, order_id, total_amount, order_date
FROM orders
WHERE order_date BETWEEN '2026-04-01' AND '2026-06-30'
  AND total_amount >= 300000
ORDER BY total_amount DESC;

Câu query này dùng BETWEEN cho khoảng ngày và ORDER BY ... DESC để sắp xếp đơn lớn lên đầu. Khi nhìn dữ liệu, bạn phát hiện chỉ có khoảng 8% đơn đạt mốc 300.000đ — nghĩa là ngưỡng miễn phí ship này sẽ tác động đến rất ít giao dịch, có thể không tạo ra động lực mua hàng như PO kỳ vọng.

Bài học: SQL ở đây không phải để code, mà để bảo vệ requirement. Thay vì viết một spec dựa trên con số tự nghĩ ra, bạn mang dữ liệu thật vào bàn họp. Một câu WHERE đơn giản với BETWEEN và ngưỡng số tiền đã đủ để định lượng tác động kinh doanh của một quy tắc.

Tình huống 3 — Ngân hàng số: truy vết giao dịch lỗi

Bạn là BA tại một ngân hàng số đang điều tra phàn nàn của khách về chuyển khoản thất bại. Có hai bảng: accounts (account_id, customer_name, branch) và transactions (txn_id, account_id, amount, status, txn_time). Bạn muốn biết những giao dịch lỗi gần đây thuộc về khách hàng nào, ở chi nhánh nào.

SELECT a.customer_name, a.branch, t.txn_id, t.amount, t.txn_time
FROM transactions t
JOIN accounts a ON t.account_id = a.account_id
WHERE t.status = 'failed'
  AND t.txn_time >= '2026-06-01'
ORDER BY t.txn_time DESC;

Đây là INNER JOIN hợp lý: mọi giao dịch đều phải gắn với một tài khoản, nên không có nguy cơ bỏ sót dòng. Câu query ghép tên khách và chi nhánh vào danh sách giao dịch lỗi, giúp bạn nhận ra một mẫu hình: phần lớn lỗi tập trung ở các giao dịch trên 50 triệu — gợi ý một vấn đề ở ngưỡng xác thực OTP cho giao dịch lớn.

Bài học: JOIN cho phép bạn kể một câu chuyện đầy đủ từ nhiều mảnh dữ liệu rời rạc. Khi đi điều tra một issue, BA biết JOIN có thể tự đào sâu đến tận gốc rễ thay vì chỉ mô tả triệu chứng.

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

Đây là quy trình để bạn viết một câu query đúng ngay từ đầu, áp dụng được cho bất kỳ tình huống nào ở trên:

  • Phát biểu câu hỏi bằng tiếng Việt trước. Ví dụ: "Tôi muốn biết tên những khách hàng có đơn hàng thất bại trong tháng 6." Đừng vội gõ SQL khi câu hỏi còn mơ hồ.
  • Xác định mình cần những cột nào và từ bảng nào. Tên khách (bảng accounts), thông tin giao dịch (bảng transactions). Hai bảng → cần JOIN.
  • Tìm cột nối giữa các bảng. Cả hai có account_id — đó là khóa để JOIN.
  • Dựng khung query theo thứ tự SELECTFROMJOINWHERE. Viết phần FROMJOIN trước để chắc chắn dữ liệu được ghép đúng, rồi mới thêm điều kiện lọc.
  • Chạy thử với LIMIT 10. Xem cấu trúc kết quả có hợp lý không trước khi bỏ LIMIT để lấy toàn bộ.
  • Thêm WHERE từng điều kiện một. Đừng nhồi 5 điều kiện cùng lúc. Thêm từng cái, chạy lại, quan sát số dòng thay đổi. Cách này giúp bạn phát hiện ngay nếu một điều kiện lọc nhầm.
  • Đối chiếu kết quả với cảm nhận thực tế. Nếu bạn biết hệ thống có khoảng 10.000 khách mà query trả về 3 dòng, gần như chắc chắn có gì đó sai — thường là JOIN sai loại hoặc điều kiện WHERE quá chặt.

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

Dùng = NULL thay vì IS NULL. Đây là lỗi phổ biến nhất với người mới. WHERE verified_at = NULL luôn trả về 0 dòng, không báo lỗi, khiến bạn tưởng "không có ai" trong khi thực tế ngược lại. Luôn dùng IS NULL / IS NOT NULL.

Nhầm INNER JOIN với LEFT JOIN. Khi bạn muốn tìm "những người chưa làm gì" mà lại dùng INNER JOIN, những người đó bị loại khỏi kết quả và con số của bạn sẽ sai một cách âm thầm. Quy tắc ghi nhớ: cần tìm cái "thiếu", "chưa có", "bị bỏ sót" → nghĩ ngay đến LEFT JOIN + IS NULL.

Quên WHERE khi định lọc, hoặc lọc nhầm bảng. Trong câu có JOIN, hãy luôn ghi rõ alias (ví dụ t.status chứ không chỉ status) để tránh nhầm lẫn khi hai bảng có cột trùng tên.

Quên LIMIT khi khám phá. Chạy SELECT * trên bảng giao dịch 50 triệu dòng có thể làm treo công cụ và làm phiền team Data. Luôn LIMIT khi chỉ muốn xem thử.

Lẫn lộn ANDOR. WHERE status = 'failed' OR amount > 1000000 cho ra kết quả rất khác ... AND .... Khi kết hợp nhiều ORAND, hãy dùng dấu ngoặc đơn để nhóm rõ ràng: WHERE (a = 1 OR a = 2) AND b = 3.

Mẹo vàng: Trước khi gửi một con số đi họp, hãy chạy thêm một query "kiểm chứng ngược". Ví dụ nếu bạn đếm được 4.200 user chưa KYC trên tổng 10.000, hãy đếm luôn số đã KYC và kiểm tra 4.200 + số-đã-KYC có bằng 10.000 không. Thói quen đối chiếu này giúp bạn tránh mang số sai vào quyết định kinh doanh.

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

Giả sử bạn có hai bảng của một sàn TMĐT:

  • customers (customer_id, full_name, city, registered_at)
  • orders (order_id, customer_id, total_amount, status, order_date)
Hãy tự viết query cho các yêu cầu sau (rồi đối chiếu logic với phần Khái niệm cốt lõi):

  • Liệt kê tên và thành phố của tất cả khách hàng ở "Hà Nội" hoặc "TP HCM". (Gợi ý: IN.)
  • Tìm tất cả đơn hàng có giá trị từ 500.000đ đến 2.000.000đ, sắp xếp giảm dần theo giá trị. (Gợi ý: BETWEEN, ORDER BY DESC.)
  • Lấy danh sách đơn hàng kèm tên khách hàng đặt đơn (ghép hai bảng). (Gợi ý: JOIN ... ON.)
  • Tìm những khách hàng chưa từng đặt đơn nào. (Gợi ý: LEFT JOIN + IS NULL — đây là dạng câu hỏi quan trọng nhất với BA.)
  • Đếm số đơn hàng có trạng thái cancelled trong tháng 5/2026. (Gợi ý: kết hợp WHERE với điều kiện ngày và trạng thái.)
Khi làm xong, hãy tự đặt câu hỏi: nếu câu 3 cho ra ít dòng hơn tổng số đơn, điều đó nói lên dữ liệu của bạn có vấn đề gì?

Tóm tắt

SQL là công cụ giúp BA chuyển từ vị thế "người chờ dữ liệu" sang "người tự tìm câu trả lời". Ba viên gạch nền tảng:

  • SELECT chọn cột và bảng bạn muốn xem; dùng LIMIT khi khám phá.
  • WHERE là nơi business rule gặp dữ liệu — nắm vững =, BETWEEN, IN, LIKE, và đặc biệt là IS NULL.
  • JOIN ghép dữ liệu rời rạc thành câu chuyện hoàn chỉnh; phân biệt rõ INNER (chỉ giữ phần khớp) và LEFT (giữ cả phần thiếu) là kỹ năng quyết định độ chính xác của con số.
Giá trị lớn nhất không nằm ở cú pháp, mà ở thói quen: dùng dữ liệu thật để validate giả định và bảo vệ requirement, thay vì tranh luận bằng cảm tính. Khi đã quen với bộ ba này, bạn sẽ sẵn sàng cho các kỹ thuật tổng hợp dữ liệu mạnh hơn — GROUP BY, hàm aggregate và window function — ở bài tiếp theo.