Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là BA của một dự án ví điện tử, và đội phát triển hỏi bạn một câu tưởng chừng đơn giản: "Một người dùng có thể có bao nhiêu tài khoản ngân hàng liên kết? Một tài khoản ngân hàng có thể dùng cho nhiều ví không?". Nếu bạn trả lời mơ hồ, developer sẽ tự đoán, và ba tháng sau hệ thống vỡ trận khi một số điện thoại bị liên kết với hai ví khác nhau, gây ra lỗi đối soát hàng tỷ đồng.
Đây chính là lý do Data Modeling — mà cụ thể là ERD (Entity Relationship Diagram) — là một trong những kỹ năng "phần cứng" quan trọng nhất của một BA giỏi. Nhiều BA chỉ tập trung vào quy trình nghiệp vụ (process) mà quên rằng mọi quy trình đều vận hành trên dữ liệu. Khi bạn vẽ được một ERD sạch sẽ, bạn không chỉ giúp developer thiết kế database đúng, mà còn ép chính mình phải hiểu nghiệp vụ đến tận gốc: thực thể nào tồn tại, chúng quan hệ với nhau ra sao, ràng buộc nào là bắt buộc.
Trong các bài trước bạn đã học cách viết requirement (Bài 28), viết NFR (Bài 34), và cách dùng SQL để truy vấn dữ liệu (Bài 41–42). Bài này là mảnh ghép còn thiếu: cách mô hình hóa cấu trúc dữ liệu trước khi nó tồn tại. ERD là ngôn ngữ chung giữa BA, kiến trúc sư hệ thống và developer. Nắm vững nó, bạn nói chuyện với đội kỹ thuật bằng ngôn ngữ của họ.
Khái niệm cốt lõi
Entity (thực thể) là gì
Entity là một "vật" trong thế giới nghiệp vụ mà ta cần lưu trữ thông tin về nó. Trong database, mỗi entity thường tương ứng với một bảng (table). Ví dụ trong hệ thống thương mại điện tử: KhachHang, DonHang, SanPham, ThanhToan đều là entity.
Một mẹo để nhận diện entity: nó thường là danh từ trong câu mô tả nghiệp vụ, và bạn cần lưu nhiều bản ghi (instance) của nó. "Khách hàng đặt đơn hàng chứa sản phẩm" — ba danh từ in đậm chính là ba entity ứng viên.
Attribute (thuộc tính)
Attribute là các đặc tính mô tả entity — chính là các cột (column) trong bảng. Entity KhachHang có thể có: ma_khach_hang, ho_ten, so_dien_thoai, email, ngay_dang_ky.
Có hai loại attribute đặc biệt cần nắm:
- Primary Key (PK) — khóa chính: thuộc tính (hoặc tổ hợp thuộc tính) định danh duy nhất mỗi bản ghi. Ví dụ
ma_khach_hang. PK không được trùng, không được null. - Foreign Key (FK) — khóa ngoại: thuộc tính tham chiếu đến PK của một bảng khác, tạo nên mối quan hệ. Ví dụ bảng
DonHangcó cộtma_khach_hangtrỏ về bảngKhachHang.
Relationship (mối quan hệ) và cardinality
Đây là phần "linh hồn" của ERD. Relationship mô tả cách các entity liên kết với nhau, và cardinality (lực lượng) cho biết số lượng bản ghi tham gia ở mỗi phía. Có ba loại cơ bản:
- 1:1 (one-to-one) — Một bản ghi A tương ứng đúng một bản ghi B. Ví dụ: một
NhanViencó đúng mộtHoSoLuong. Loại này hiếm, thường dùng để tách dữ liệu nhạy cảm hoặc ít truy cập. - 1:N (one-to-many) — Một bản ghi A liên kết với nhiều bản ghi B, nhưng mỗi B chỉ thuộc một A. Đây là loại phổ biến nhất. Ví dụ: một
KhachHangcó nhiềuDonHang, nhưng mỗiDonHangthuộc về đúng mộtKhachHang. - M:N (many-to-many) — Nhiều A liên kết nhiều B. Ví dụ: một
DonHangchứa nhiềuSanPham, và mộtSanPhamxuất hiện trong nhiềuDonHang. Quan hệ M:N không thể biểu diễn trực tiếp trong database quan hệ — ta phải tách thành một bảng trung gian (junction/associative table), ví dụChiTietDonHang.
Notation Crow's Foot — ký pháp chân quạ
Đây là cách vẽ ERD phổ biến nhất hiện nay (Lucidchart, Draw.io, dbdiagram.io đều dùng). Mỗi đầu của đường nối có ký hiệu thể hiện cardinality:
Ký hiệu ở đầu đường nối (Crow's Foot): ─||─ : đúng một (one and only one)
─o|─ : không hoặc một (zero or one)
─|< : một hoặc nhiều (one or many)
─o< : không hoặc nhiều (zero or many)
Ví dụ quan hệ KhachHang — DonHang đọc là: "Một KhachHang có không hoặc nhiều DonHang; một DonHang thuộc về đúng một KhachHang":
KhachHang ─||────────o< DonHang
(một và chỉ một) (không hoặc nhiều)
Hai ký hiệu phần trong (gần đường) thể hiện cardinality tối đa (một hay nhiều — bằng vạch thẳng hay chân quạ), hai ký hiệu phần ngoài (vạch hoặc vòng tròn) thể hiện modality / participation (bắt buộc hay tùy chọn). Đây là điểm tinh tế mà nhiều BA bỏ qua: "bắt buộc hay tùy chọn" cũng là một requirement nghiệp vụ. Một DonHang bắt buộc phải có khách hàng (không thể null), nhưng một KhachHang mới đăng ký có thể chưa có đơn hàng nào.
Ba mức độ của data model
BA cần phân biệt ba mức trừu tượng, vì bạn làm việc chủ yếu ở hai mức đầu:
- Conceptual model: chỉ có entity và relationship, không có chi tiết cột. Dùng để thống nhất với stakeholder nghiệp vụ.
- Logical model: thêm attribute, PK, FK, kiểu quan hệ — nhưng độc lập với công nghệ database. Đây là "đất diễn" chính của BA.
- Physical model: thêm kiểu dữ liệu (VARCHAR, INT), index, constraint cụ thể của hệ quản trị (MySQL, PostgreSQL). Thường do DBA/developer hoàn thiện.
Tình huống thực tế
Ví dụ 1 — Sàn TMĐT Tiki: bài toán quan hệ M:N của đơn hàng
Giả sử bạn là BA cho module đặt hàng của một sàn như Tiki. Stakeholder mô tả: "Khách đặt một đơn, trong đơn có nhiều sản phẩm, mỗi sản phẩm có số lượng và giá tại thời điểm mua".
BA non kinh nghiệm sẽ vẽ thẳng DonHang ─o< SanPham và nghĩ là xong. Nhưng câu hỏi đặt ra: lưu số lượng và giá tại thời điểm mua ở đâu? Không thể lưu trong SanPham (vì giá đó là giá hiện tại, không phải giá lúc mua), cũng không thể lưu trong DonHang (vì mỗi đơn có nhiều sản phẩm với số lượng khác nhau).
Lời giải là tách quan hệ M:N thành bảng trung gian ChiTietDonHang:
DonHang ─||────o< ChiTietDonHang >o────||─ SanPhamChiTietDonHang:
- ma_chi_tiet (PK)
- ma_don_hang (FK -> DonHang)
- ma_san_pham (FK -> SanPham)
- so_luong
- gia_tai_thoi_diem_mua <-- snapshot giá!
Bài học rút ra: Mọi quan hệ M:N đều cần bảng trung gian, và bảng trung gian thường là nơi "ẩn náu" của các attribute nghiệp vụ quan trọng (số lượng, giá snapshot, ngày). Khi giá sản phẩm tăng từ 200.000đ lên 250.000đ tuần sau, đơn cũ vẫn phải hiển thị đúng 200.000đ — chỉ có thiết kế snapshot mới đảm bảo điều đó. Đây là một quyết định nghiệp vụ, không phải kỹ thuật, nên BA phải làm rõ.
Ví dụ 2 — Ngân hàng VPBank: quan hệ 1:1 để bảo vệ dữ liệu nhạy cảm
Một dự án mở tài khoản số tại ngân hàng. Mỗi KhachHang có thông tin định danh eKYC: số CCCD, ảnh chân dung, kết quả đối chiếu khuôn mặt. Đây là dữ liệu cực nhạy cảm, bị kiểm soát chặt bởi Nghị định về bảo vệ dữ liệu cá nhân.
Thay vì nhồi tất cả vào bảng KhachHang, đội thiết kế tách ra:
KhachHang ─||────||─ ThongTinDinhDanh (quan hệ 1:1)KhachHang: ma_kh (PK), ho_ten, so_dien_thoai, email
ThongTinDinhDanh: ma_kh (PK, FK), so_cccd, anh_chan_dung, ket_qua_ekyc
Bài học rút ra: Quan hệ 1:1 ít gặp nhưng cực hữu ích khi bạn cần (1) tách dữ liệu nhạy cảm để áp policy bảo mật/mã hóa riêng, (2) tách các cột ít truy cập để bảng chính nhẹ và nhanh. Lưu ý PK của bảng phụ chính là FK trỏ về bảng chính — đây là dấu hiệu kinh điển của 1:1. Là BA, khi gặp yêu cầu compliance như "dữ liệu CCCD phải được mã hóa và phân quyền truy cập riêng", bạn nên đề xuất tách bảng ngay từ ERD.
Ví dụ 3 — Grab: quan hệ tự tham chiếu trong cây giới thiệu (referral)
Grab có chương trình "giới thiệu bạn bè": một tài xế giới thiệu nhiều tài xế khác, và mỗi tài xế được giới thiệu bởi (tối đa) một người. Cả người giới thiệu lẫn người được giới thiệu đều nằm trong cùng entity TaiXe.
TaiXe ─o|────o< TaiXe (self-referencing / unary relationship)TaiXe: ma_tai_xe (PK), ho_ten, ma_nguoi_gioi_thieu (FK -> TaiXe.ma_tai_xe)
Một tài xế có thể giới thiệu 0 hoặc nhiều tài xế; mỗi tài xế được giới thiệu bởi 0 hoặc 1 tài xế (người đăng ký đầu tiên không có ai giới thiệu, nên FK cho phép null).
Bài học rút ra: Quan hệ một entity quan hệ với chính nó (self-referencing / unary) xuất hiện thường xuyên hơn bạn nghĩ: sơ đồ tổ chức (nhân viên — quản lý), danh mục lồng nhau (category cha — category con), cây giới thiệu MLM. Khi thấy "X quản lý X khác" hoặc "X giới thiệu X khác", hãy nghĩ ngay đến self-reference với một FK trỏ về chính bảng đó.
Hướng dẫn từng bước
Quy trình xây dựng một ERD từ mô tả nghiệp vụ:
- Thu thập và gạch chân danh từ. Lấy tài liệu mô tả nghiệp vụ, gạch chân mọi danh từ. Đây là danh sách ứng viên entity và attribute. "Khách hàng đặt đơn hàng gồm nhiều sản phẩm, thanh toán qua ví" → khách hàng, đơn hàng, sản phẩm, ví.
- Lọc entity thật sự. Hỏi: "Tôi có cần lưu nhiều bản ghi của nó, với các thuộc tính riêng không?" Nếu có → entity. Nếu nó chỉ là một thuộc tính đơn (ví dụ "màu sắc") → đó là attribute, không phải entity.
- Xác định attribute và Primary Key cho mỗi entity. Mỗi entity cần một PK định danh duy nhất. Ưu tiên surrogate key (mã tự sinh như
ma_kh) thay vì natural key (số điện thoại) vì natural key dễ thay đổi.
- Xác định relationship và cardinality. Với từng cặp entity, đặt hai câu hỏi: "Một A liên kết với bao nhiêu B?" và ngược lại "Một B liên kết với bao nhiêu A?". Hai câu trả lời cho bạn loại quan hệ (1:1, 1:N, M:N).
- Xác định modality (bắt buộc/tùy chọn). Với mỗi đầu quan hệ, hỏi "tối thiểu là 0 hay 1?". Đây là ràng buộc nghiệp vụ — đừng bỏ qua.
- Xử lý quan hệ M:N. Mọi M:N tách thành bảng trung gian. Đặt tên bảng theo nghiệp vụ (
ChiTietDonHang,DangKyKhoaHoc) và thêm các attribute thuộc về mối quan hệ vào đó.
- Đặt FK đúng chỗ. Trong quan hệ 1:N, FK luôn nằm ở phía "nhiều" (many).
DonHangchứama_khach_hang, không phải ngược lại.
- Review với stakeholder và developer. Dùng conceptual/logical model để xác nhận nghiệp vụ với business, rồi giao logical model cho developer hoàn thiện physical. Vẽ bằng công cụ như dbdiagram.io, Lucidchart hay Draw.io (sẽ học kỹ ở Bài 54).
Lỗi thường gặp & mẹo
- Vẽ M:N trực tiếp rồi để developer "tự hiểu". Hậu quả là mất các attribute quan trọng của mối quan hệ (số lượng, giá snapshot). Luôn tự tay tách bảng trung gian trong logical model.
- Quên modality. Chỉ ghi 1:N mà không nói rõ đầu nào bắt buộc. "Đơn hàng bắt buộc có khách" vs "khách có thể chưa có đơn" là hai ràng buộc khác nhau và ảnh hưởng đến việc cột FK có cho phép null hay không.
- Nhầm attribute thành entity (và ngược lại). "Trạng thái đơn hàng" nếu chỉ là một giá trị (Đang xử lý / Đã giao) thì là attribute; nhưng nếu cần lưu lịch sử chuyển trạng thái kèm thời gian và người thực hiện, nó trở thành entity
LichSuTrangThai. Quyết định dựa trên nghiệp vụ, không phải cảm tính.
- Dùng natural key làm PK. Lấy số điện thoại làm khóa chính khách hàng, rồi khách đổi số → vỡ toàn bộ tham chiếu. Mẹo: luôn dùng surrogate key (ID tự sinh) làm PK, và đặt unique constraint riêng cho số điện thoại nếu cần.
- Bỏ qua quan hệ self-referencing. Khi gặp cấu trúc cây (tổ chức, danh mục, giới thiệu), nhiều BA tạo nhiều bảng thừa. Một FK trỏ về chính bảng đó là đủ.
- Mẹo đặt tên nhất quán. Thống nhất quy ước ngay từ đầu: bảng số ít hay số nhiều, FK đặt theo mẫu
ma_<bang>. Sự nhất quán giúp developer đọc ERD nhanh và giảm hiểu nhầm.
- Mẹo "đọc to" mỗi quan hệ. Trước khi chốt, đọc thành câu hoàn chỉnh: "Một khách hàng có không hoặc nhiều đơn hàng; một đơn hàng thuộc về đúng một khách hàng". Nếu câu nghe vô lý về mặt nghiệp vụ, ERD của bạn đang sai.
Bài tập thực hành
Bối cảnh: Bạn là BA cho một nền tảng học trực tuyến (giống chính nền tảng bạn đang học). Mô tả nghiệp vụ:
> "Học viên đăng ký nhiều khóa học. Mỗi khóa học do một giảng viên phụ trách, nhưng một giảng viên có thể dạy nhiều khóa. Mỗi khóa học gồm nhiều bài học. Sau khi học, học viên có thể để lại đánh giá (rating + bình luận) cho khóa học. Khi đăng ký, hệ thống lưu ngày đăng ký và tiến độ hoàn thành của học viên trong khóa đó."
Yêu cầu:
- Liệt kê các entity và attribute chính (gồm PK).
- Xác định loại quan hệ (1:1 / 1:N / M:N) và modality cho từng cặp.
- Chỉ ra (các) bảng trung gian cần thiết và attribute của nó.
- Vẽ ERD bằng ký pháp Crow's Foot (có thể dùng dbdiagram.io).
DangKy chứa ngay_dang_ky, tien_do). Quan hệ "giảng viên — khóa học" là 1:N. "Khóa học — bài học" là 1:N. "Đánh giá" có thể là entity riêng nối học viên với khóa học. Hãy đọc to từng quan hệ để tự kiểm tra.Tóm tắt
ERD là công cụ giúp BA mô hình hóa cấu trúc dữ liệu của hệ thống trước khi nó được xây dựng — một kỹ năng "phần cứng" tách biệt BA giỏi khỏi BA chỉ biết viết tài liệu. Ba thành phần cốt lõi là entity (bảng), attribute (cột, gồm PK và FK), và relationship với cardinality (1:1, 1:N, M:N) cùng modality (bắt buộc/tùy chọn).
Quy tắc vàng cần nhớ: mọi quan hệ M:N phải tách thành bảng trung gian, vốn thường là nơi chứa các attribute nghiệp vụ quan trọng; trong quan hệ 1:N, FK nằm ở phía "nhiều"; quan hệ 1:1 hữu ích để tách dữ liệu nhạy cảm; và self-referencing xử lý các cấu trúc cây. Luôn ưu tiên surrogate key làm PK, luôn xác định rõ modality, và luôn "đọc to" mỗi quan hệ để kiểm tra tính hợp lý nghiệp vụ.
Khi bạn vẽ được một ERD sạch sẽ, bạn không chỉ giúp developer thiết kế database đúng — bạn còn chứng minh mình hiểu nghiệp vụ đến tận xương tủy. Ở Bài 54, bạn sẽ luyện vẽ ERD thành thạo trên các công cụ chuyên dụng. Còn ngay bây giờ, hãy cầm một mô tả nghiệp vụ bất kỳ và thử gạch chân danh từ — đó là bước đầu tiên của mọi data model.