Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn vừa được giao một câu hỏi quen thuộc: "Doanh thu tháng này tăng hay giảm so với tháng trước?". Bạn mở database lên, thấy có một cột tên là revenue, một cột tên là gmv, và một cột tên là net_amount. Cả ba đều là số tiền. Cả ba đều "có vẻ" là doanh thu. Bạn chọn đại một cột, viết query, ra con số đẹp đẽ, gửi cho sếp. Hai tuần sau, kế toán phát hiện con số bạn báo cáo lệch 18% so với sổ sách, vì gmv bao gồm cả đơn đã huỷ và đơn hoàn tiền, còn net_amount mới là doanh thu thực thu.
Đây không phải lỗi SQL. Câu query của bạn chạy hoàn hảo. Đây là lỗi không hiểu dữ liệu — và nó là loại lỗi đắt giá nhất, khó phát hiện nhất trong nghề Business Analyst. Suốt 49 bài trước, bạn đã học cách viết query thành thạo: JOIN, GROUP BY, window function, CTE. Nhưng tất cả kỹ năng đó đều vô nghĩa nếu bạn không biết cột revenue thật sự nghĩa là gì.
Bài này nói về thứ giúp bạn không bao giờ rơi vào tình huống trên: data dictionary (từ điển dữ liệu) và văn hoá documentation cho BA. Đây là phần ít được dạy nhất nhưng lại phân biệt rõ ràng một BA nghiệp dư với một BA mà cả team tin tưởng. Một BA giỏi không chỉ trả lời được câu hỏi, mà còn biết chắc câu trả lời của mình đúng, và để lại tài liệu để người sau không phải đoán mò như mình từng đoán.
Khái niệm cốt lõi
Data dictionary là gì?
Data dictionary là một tài liệu mô tả có cấu trúc về dữ liệu trong hệ thống: có những bảng nào, mỗi bảng chứa cột gì, mỗi cột mang ý nghĩa gì, kiểu dữ liệu ra sao, giá trị hợp lệ là những gì, và các cột liên kết với nhau thế nào. Nói ngắn gọn, nó là bản đồ giải thích dữ liệu để bất kỳ ai — BA mới, dev mới, hay chính bạn của sáu tháng sau — đều hiểu được dữ liệu mà không cần đi hỏi từng người.
Điểm quan trọng nhất cần ghi nhớ: data dictionary là single source of truth (nguồn chân lý duy nhất). Khi có tranh cãi "doanh thu được tính thế nào", người ta không cãi nhau bằng trí nhớ — họ mở data dictionary ra đọc. Khi định nghĩa thay đổi, người ta sửa ở data dictionary, và tất cả mọi người dùng theo bản đã sửa.
Ba tầng tài liệu mà BA cần phân biệt
Nhiều người gộp chung "documentation" thành một khối, nhưng thực ra có ba tầng riêng biệt, phục vụ ba mục đích khác nhau:
Tầng 1 — Data dictionary (từ điển dữ liệu): Mô tả dữ liệu thô. Bảng, cột, kiểu, ràng buộc. Đây là tầng kỹ thuật nhất, mô tả "dữ liệu trông như thế nào".
Tầng 2 — Metrics definition (định nghĩa chỉ số): Mô tả cách tính các con số nghiệp vụ từ dữ liệu thô. Ví dụ: "Active User = user có ít nhất 1 đơn hàng thành công trong 30 ngày gần nhất". Đây là tầng trừu tượng hơn, nối dữ liệu với nghiệp vụ. Tầng này thường quan trọng với BA hơn cả tầng 1, vì 90% cãi vã trong công ty là về cách tính metric chứ không phải về cột nào lưu cái gì.
Tầng 3 — Query/report documentation (tài liệu báo cáo): Mô tả các báo cáo, dashboard, query đã được xây sẵn — chúng dùng để làm gì, ai là người sở hữu, cập nhật bao lâu một lần. Tầng này giúp người dùng cuối tự phục vụ mà không hỏi BA mỗi ngày.
Một BA trưởng thành đóng góp vào cả ba tầng. Trong bài này chúng ta tập trung sâu nhất vào tầng 1 và tầng 2, vì đó là phần BA trực tiếp tạo ra giá trị.
Một dòng trong data dictionary gồm những gì?
Đây là format phổ biến nhất, dạng bảng. Mỗi dòng mô tả một cột:
| Cột | Ý nghĩa | Bắt buộc? |
|---|---|---|
| Bảng (Table) | Tên bảng chứa cột | Có |
| Cột (Column) | Tên cột | Có |
| Kiểu dữ liệu (Data type) | INT, VARCHAR, DATETIME, DECIMAL... | Có |
| Mô tả (Description) | Cột này nghĩa là gì, bằng ngôn ngữ nghiệp vụ | Có |
| Giá trị hợp lệ (Allowed values) | Danh sách giá trị hoặc khoảng giá trị | Nên có |
| Nullable? | Cột có thể NULL không, và NULL nghĩa là gì | Nên có |
| Ví dụ (Example) | Một giá trị mẫu | Nên có |
| Khoá/Quan hệ (Key/FK) | Là PK, FK trỏ tới bảng nào | Nên có |
| Ghi chú (Notes) | Cảnh báo, đơn vị tiền tệ, múi giờ, edge case | Tuỳ |
status = 3 nghĩa là đơn đã giao thành công nhưng chưa đối soát tài chính" — chỉ con người mới viết được, và đó chính là giá trị thật của data dictionary.Vì sao đơn vị, múi giờ và edge case là "vàng"
Ba thứ gây ra nhiều sự cố báo cáo nhất tại Việt Nam và Đông Nam Á:
- Đơn vị tiền tệ và bội số: Cột
amountlưu VND hay USD? Lưu đồng hay lưu nghìn đồng? Nhiều hệ thống lưu tiền dưới dạng số nguyên đơn vị nhỏ nhất (ví dụ lưu50000nghĩa là 50.000 VND, nhưng cũng có hệ thống lưu5000000nghĩa là 50.000,00 với hai chữ số thập phân ngầm). Sai chỗ này là lệch 100 lần. - Múi giờ: Cột
created_atlưu theo UTC hay theo giờ Việt Nam (UTC+7)? Một báo cáo "đơn theo ngày" sẽ sai lệch hoàn toàn ở các đơn đặt lúc 23h-7h sáng nếu bạn không quy đổi múi giờ. - Soft delete và trạng thái ẩn: Bảng có cột
is_deletedhaydeleted_atkhông? Nếu có mà bạn quên lọc, bạn sẽ đếm cả những bản ghi đã bị xoá logic.
Tình huống thực tế
Ví dụ 1 — Sàn TMĐT mô phỏng kiểu Tiki: ba cột "doanh thu" và cái giá của việc không có data dictionary
Một công ty TMĐT giả định tên "ChợViệt", quy mô khoảng 2 triệu đơn/tháng. Bảng orders của họ có ba cột tiền: gmv (tổng giá trị hàng hoá theo giá niêm yết), paid_amount (số tiền khách thực trả sau khuyến mãi và voucher), và settled_amount (số tiền thực thu sau khi trừ hoàn tiền và đơn huỷ, dùng để đối soát với nhà bán).
Phòng Marketing báo cáo "doanh thu" dùng gmv để con số trông to. Phòng Tài chính dùng settled_amount. Ban giám đốc nhận hai con số chênh nhau 23% cho cùng một tháng và mất nguyên buổi họp để tranh cãi xem ai đúng. Không ai sai về SQL cả — họ chỉ định nghĩa "doanh thu" khác nhau.
Diễn giải: Sau sự cố, BA của ChợViệt xây một data dictionary có dòng:
| Cột | Mô tả | Ghi chú |
|---|---|---|
gmv | Tổng giá niêm yết, chưa trừ khuyến mãi. Dùng cho báo cáo quy mô thị trường, KHÔNG dùng làm doanh thu. | Đơn vị: VND. Bao gồm cả đơn sau đó bị huỷ. |
paid_amount | Tiền khách thực trả sau voucher. Dùng cho phân tích hành vi thanh toán. | Đơn vị: VND. Chưa trừ hoàn tiền. |
settled_amount | Doanh thu thực thu sau hoàn/huỷ. Là con số doanh thu chính thức. | Đơn vị: VND. NULL nếu đơn chưa đối soát xong (thường T+15). |
Ví dụ 2 — Fintech kiểu ví điện tử: cột status mã hoá bằng số và cái bẫy của tri thức ngầm
Một ví điện tử giả định tên "ViPay" có bảng transactions với cột status kiểu INT, giá trị từ 0 đến 7. Anh dev kỳ cựu nhớ hết trong đầu: 0 là khởi tạo, 1 là chờ xử lý, 2 là thành công, 3 là thất bại, 4 là hoàn tiền một phần, 5 là hoàn tiền toàn bộ, 6 là treo nghi ngờ gian lận, 7 là huỷ. BA mới vào, viết query đếm "giao dịch thành công" với điều kiện status >= 2, vô tình gộp luôn cả giao dịch thất bại, hoàn tiền và gian lận vào, thổi phồng số liệu lên 40%.
Diễn giải: Vấn đề ở đây là tri thức ngầm (tribal knowledge) — kiến thức chỉ nằm trong đầu một vài người. Khi anh dev kỳ cựu nghỉ việc, cả công ty mù về ý nghĩa cột status. Một data dictionary tốt phải liệt kê đầy đủ enum:
| Giá trị | Ý nghĩa | Có tính là giao dịch thành công? |
|---|---|---|
| Khởi tạo | Không | |
| 1 | Chờ xử lý | Không |
| 2 | Thành công | Có |
| 3 | Thất bại | Không |
| 4 | Hoàn một phần | Có (một phần) |
| 5 | Hoàn toàn bộ | Không |
| 6 | Treo nghi ngờ gian lận | Không |
| 7 | Huỷ | Không |
status = 2, không phải status >= 2. Định nghĩa metric "tỷ lệ giao dịch thành công" cũng được ghi rõ luôn ở tầng 2.Bài học: Mọi cột mã hoá bằng số hoặc mã viết tắt đều là một quả mìn chờ nổ. Liệt kê đầy đủ enum trong data dictionary là cách rẻ nhất để gỡ mìn.
Ví dụ 3 — Ride-hailing kiểu Grab/Be: metric "chuyến hoàn thành" và sự lệch pha giữa hai phòng ban
Một nền tảng gọi xe giả định tên "BeGo" có hai phòng cùng báo cáo số "chuyến hoàn thành" hàng tuần. Phòng Vận hành định nghĩa chuyến hoàn thành là tài xế bấm nút "đã đến nơi". Phòng Tài chính định nghĩa là chuyến có thanh toán thành công. Hai con số lệch nhau khoảng 6% mỗi tuần vì có chuyến tài xế bấm hoàn thành nhưng khách huỷ thanh toán hoặc thanh toán lỗi.
Diễn giải: Đây là vấn đề ở tầng 2 — metrics definition, không phải tầng dữ liệu thô. BA của BeGo lập một bảng định nghĩa metric chuẩn:
| Metric | Định nghĩa chính thức | Công thức (logic) | Chủ sở hữu |
|---|---|---|---|
| Completed Trip (Vận hành) | Chuyến mà tài xế xác nhận trả khách | trip_status = 'dropped_off' | Trưởng phòng Vận hành |
| Billable Trip (Tài chính) | Chuyến hoàn thành VÀ thanh toán thành công | trip_status = 'dropped_off' AND payment_status = 'paid' | Trưởng phòng Tài chính |
Bài học: Đừng cố ép một cái tên ("chuyến hoàn thành") gánh hai định nghĩa. Khi nghiệp vụ cần phân biệt, hãy đặt tên riêng và ghi rõ chủ sở hữu cho từng metric. Chủ sở hữu là người có quyền quyết định khi định nghĩa cần thay đổi.
Hướng dẫn từng bước
Đây là quy trình thực tế để xây data dictionary cho một database bạn vừa được giao, kể cả khi nó hoàn toàn không có tài liệu.
Bước 1 — Lấy cấu trúc thô từ chính database. Đừng gõ tay danh sách bảng và cột. Database đã có sẵn metadata. Trong MySQL/PostgreSQL bạn dùng INFORMATION_SCHEMA:
SELECT table_name, column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'ten_database'
ORDER BY table_name, ordinal_position;
Kết quả này cho bạn khung xương: tên bảng, tên cột, kiểu, nullable. Export ra Google Sheets làm bộ khung ban đầu.
Bước 2 — Ưu tiên theo mức độ quan trọng, không làm dàn trải. Một database có thể có 200 bảng nhưng BA thường chỉ động vào 15-20 bảng cốt lõi (orders, users, transactions, products...). Hãy bắt đầu tài liệu hoá những bảng bạn dùng nhiều nhất. Tài liệu 20% bảng quan trọng phục vụ 80% nhu cầu — đừng cầu toàn ngay từ đầu.
Bước 3 — Điền phần mô tả nghiệp vụ bằng cách phỏng vấn và quan sát dữ liệu. Đây là phần máy không làm thay được. Với mỗi cột quan trọng, hãy hỏi dev hoặc product owner, đồng thời tự chạy query thăm dò để kiểm chứng:
-- Xem các giá trị thực tế của một cột enum để liệt kê đầy đủ
SELECT status, COUNT(*) AS so_dong
FROM orders
GROUP BY status
ORDER BY so_dong DESC;
Query này tiết lộ cột status thực tế có những giá trị nào và phân bố ra sao — nhiều khi bạn phát hiện ra giá trị mà ngay cả dev cũng quên là nó tồn tại.
Bước 4 — Ghi rõ các "vàng": đơn vị, múi giờ, NULL, soft-delete. Với mỗi cột tiền, ghi đơn vị. Với mỗi cột thời gian, ghi múi giờ. Với mỗi cột nullable, ghi NULL nghĩa là gì (không có dữ liệu? chưa xảy ra? hay không áp dụng?). Đây là nơi data dictionary tạo ra giá trị nhiều nhất.
Bước 5 — Xây tầng metrics definition cho các chỉ số hay tranh cãi. Liệt kê 10-20 metric công ty dùng thường xuyên nhất (doanh thu, active user, tỷ lệ chuyển đổi...), với mỗi metric ghi: định nghĩa bằng lời, logic tính toán, chủ sở hữu, và lưu ý.
Bước 6 — Đặt tài liệu ở nơi mọi người tìm thấy và gắn nó vào quy trình. Một data dictionary nằm trong file Excel trên máy bạn là vô dụng. Đưa nó lên nơi dùng chung (Confluence, Notion, Google Sheets chia sẻ, hoặc tích hợp trong công cụ như dbt docs nếu công ty dùng). Quan trọng hơn: khi ai hỏi bạn về một cột, đừng trả lời miệng — gửi link tới dòng tương ứng trong data dictionary. Điều này huấn luyện cả team coi data dictionary là nơi tra cứu.
Bước 7 — Lập lịch rà soát định kỳ. Database thay đổi: cột mới được thêm, ý nghĩa cũ bị bỏ. Một data dictionary lỗi thời còn nguy hiểm hơn không có, vì người ta tin nó. Hãy đặt lịch rà soát hàng quý, và gắn việc cập nhật tài liệu vào quy trình mỗi khi có thay đổi schema.
Lỗi thường gặp & mẹo
Lỗi 1 — Chỉ chép lại kiểu dữ liệu mà bỏ trống phần mô tả. Một data dictionary ghi "cột amount, kiểu DECIMAL, not null" là vô giá trị, vì nó không nói amount của cái gì, đơn vị nào. Phần kiểu dữ liệu là phần máy làm được; phần mô tả nghiệp vụ mới là phần con người phải làm và là phần đáng tiền. Nếu phải chọn, hãy ưu tiên mô tả hơn là độ phủ.
Lỗi 2 — Để tài liệu lỗi thời mà không đánh dấu. Tài liệu sai khiến người ta tin nhầm và sai theo. Mẹo: mỗi dòng nên có cột "lần cập nhật cuối" và tên người cập nhật. Khi nghi ngờ, người đọc biết tài liệu cũ tới đâu để cẩn trọng.
Lỗi 3 — Cầu toàn rồi bỏ cuộc. Nhiều BA muốn tài liệu hoá 100% database ngay, thấy quá sức rồi bỏ luôn. Mẹo: bắt đầu với một bảng quan trọng nhất, làm xong, dùng được ngay, rồi mở rộng dần. Một data dictionary chỉ có 5 bảng quan trọng nhưng được cập nhật còn quý hơn một bản đầy đủ nhưng lỗi thời.
Lỗi 4 — Lẫn lộn tầng dữ liệu và tầng metric. Đừng viết định nghĩa "Active User" vào cột mô tả của một cột thô. Dữ liệu thô và metric là hai tầng khác nhau, để lẫn sẽ khiến tài liệu rối. Tách bạch giúp người đọc biết tra ở đâu.
Mẹo vàng — Dùng chính query thăm dò làm bằng chứng. Trước khi viết "cột này không bao giờ NULL", hãy chạy SELECT COUNT(*) FROM bang WHERE cot IS NULL để chắc chắn. Data dictionary tốt nhất được viết dựa trên dữ liệu thực, không dựa trên trí nhớ của ai đó.
Mẹo — Ghi cả "không nên dùng cột này". Đôi khi thông tin giá trị nhất là cảnh báo: "Cột legacy_price đã ngừng dùng từ 2024, đừng query". Một cảnh báo như vậy tiết kiệm hàng giờ debug cho người sau.
Bài tập thực hành
- Khung xương từ schema: Chọn một database mẫu bạn đang học (hoặc một schema mô phỏng e-commerce ở các bài sau). Viết query trên
information_schema.columnsđể xuất danh sách bảng-cột-kiểu-nullable của toàn bộ database. Đưa kết quả vào Google Sheets làm khung data dictionary ban đầu.
- Khai quật enum: Chọn một cột trạng thái (ví dụ
order_statushoặcpayment_status). Viết queryGROUP BYđể liệt kê tất cả giá trị thực tế và số lượng mỗi giá trị. Dựa vào đó, viết một bảng giải thích từng giá trị nghĩa là gì và giá trị nào được tính là "thành công".
- Săn cột "vàng": Trong database mẫu, tìm tất cả cột chứa tiền và tất cả cột thời gian. Với mỗi cột, ghi giả định của bạn về đơn vị tiền tệ và múi giờ, rồi viết một query đơn giản để kiểm chứng (ví dụ: xem giá trị min/max của cột tiền có hợp lý không, xem giờ của các bản ghi sớm nhất/muộn nhất trong ngày).
- Định nghĩa metric gây tranh cãi: Chọn một metric quen thuộc như "doanh thu tháng" hoặc "active user". Viết định nghĩa chính thức bằng lời, kèm logic SQL cụ thể, và chỉ ra ít nhất hai cách hiểu sai có thể xảy ra nếu không có định nghĩa rõ ràng.
- Phát hiện rủi ro: Tìm trong database mẫu một cột nullable. Viết query đếm số dòng NULL, rồi viết một câu ghi chú giải thích NULL ở cột đó nghĩa là gì về mặt nghiệp vụ (chưa xảy ra? không áp dụng? thiếu dữ liệu?).
Tóm tắt
Data dictionary là bản đồ giúp mọi người hiểu dữ liệu giống nhau, và là single source of truth khi có tranh cãi. Nó cứu BA khỏi loại lỗi nguy hiểm nhất: query chạy đúng nhưng hiểu sai dữ liệu. Có ba tầng tài liệu cần phân biệt — từ điển dữ liệu (bảng/cột/kiểu), định nghĩa metric (cách tính chỉ số nghiệp vụ), và tài liệu báo cáo — trong đó BA đóng góp nhiều nhất vào hai tầng đầu.
Một dòng data dictionary tốt không chỉ ghi kiểu dữ liệu mà còn ghi mô tả nghiệp vụ, danh sách giá trị hợp lệ, và các "vàng" như đơn vị tiền tệ, múi giờ, ý nghĩa NULL, soft-delete. Ba ví dụ về ChợViệt, ViPay và BeGo cho thấy cùng một bài học: khi định nghĩa không rõ ràng, người ta tranh cãi bằng trí nhớ và báo cáo sai số liệu lên lãnh đạo; khi có tài liệu, mọi tranh cãi được giải quyết bằng cách mở tài liệu ra đọc.
Quy trình xây dựng thực tế: lấy khung xương từ information_schema, ưu tiên bảng quan trọng, điền mô tả nghiệp vụ dựa trên phỏng vấn và query thăm dò thực tế, ghi rõ các trường nhạy cảm, tách tầng metric, đặt tài liệu ở nơi dùng chung và rà soát định kỳ. Hãy nhớ: đừng cầu toàn — một data dictionary nhỏ nhưng sống và được cập nhật giá trị hơn một bản đầy đủ nhưng lỗi thời. Đây chính là kỹ năng biến bạn từ người viết query thành người mà cả team tin tưởng về dữ liệu.