Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn đang làm Business Analyst cho một công ty bảo hiểm. Khách hàng của họ gồm hai loại: cá nhân (có ngày sinh, số CCCD) và doanh nghiệp (có mã số thuế, ngày thành lập, người đại diện pháp luật). Cả hai đều là "khách hàng" — đều có tên, địa chỉ, số điện thoại, đều ký hợp đồng. Nhưng mỗi loại lại có những thuộc tính rất riêng mà loại kia không có.
Bạn sẽ mô hình hóa điều này như thế nào? Gộp tất cả vào một bảng Customer khổng lồ với hàng chục cột, trong đó một nửa luôn để trống tùy theo khách là cá nhân hay doanh nghiệp? Hay tách thành hai bảng hoàn toàn riêng biệt, rồi phải nhân đôi mọi logic xử lý "khách hàng nói chung"?
Đây chính là tình huống mà kỹ thuật Subtype/Supertype (tạm dịch: kiểu cha/kiểu con, hay mô hình kế thừa — inheritance) sinh ra để giải quyết. Nó là một trong những công cụ mạnh nhất nhưng cũng bị hiểu lầm nhiều nhất trong data modeling. Dùng đúng, sơ đồ của bạn vừa gọn vừa diễn đạt chính xác nghiệp vụ. Dùng sai, bạn tạo ra một mớ bòng bong cột rỗng (NULL) khiến cả đội kỹ thuật lẫn nghiệp vụ đau đầu.
Bài này sẽ giúp bạn nhận diện khi nào nên dùng mô hình kế thừa, cách vẽ nó trên ERD, và quan trọng nhất là cách "hạ" mô hình khái niệm đó xuống thành các bảng vật lý thực tế.
Khái niệm cốt lõi
Supertype và Subtype là gì
Supertype (kiểu cha / entity tổng quát) là một thực thể mang những thuộc tính và quan hệ chung cho cả một nhóm. Ví dụ: Vehicle (Phương tiện) có biển số, hãng sản xuất, năm sản xuất, chủ sở hữu.
Subtype (kiểu con / entity chuyên biệt hóa) là một biến thể cụ thể của supertype, mang thêm những thuộc tính riêng chỉ áp dụng cho nó. Ví dụ:
Car(Ô tô): số chỗ ngồi, loại nhiên liệu, có túi khí hay không.Motorcycle(Xe máy): phân khối, có yên đôi hay không.Truck(Xe tải): tải trọng tối đa, số trục.
Đối lập với "is-a" là "has-a" (có một). Một chiếc Car có một Engine (động cơ) — đây là quan hệ thành phần, không phải kế thừa. Đừng nhầm hai loại này.
Hai tính chất quan trọng: Completeness và Disjointness
Khi mô hình hóa subtype, bạn phải trả lời hai câu hỏi nghiệp vụ:
1. Tính đầy đủ (Completeness / Coverage): Mọi instance của supertype có bắt buộc phải thuộc về ít nhất một subtype không?
- Total (toàn phần / complete): Mỗi
Vehiclebắt buộc phải là Car, Motorcycle hoặc Truck — không có phương tiện nào "chung chung". Trên sơ đồ thường ký hiệu bằng hai đường gạch ngang hoặc chữ "complete". - Partial (bán phần / incomplete): Có thể tồn tại một
Vehiclekhông thuộc subtype nào đã liệt kê (ví dụ tương lai có thêm loại mới chưa định nghĩa). Ký hiệu bằng một đường gạch ngang hoặc chữ "incomplete".
- Disjoint / Exclusive (loại trừ): Một
Vehiclechỉ là một loại duy nhất — không thể vừa là Car vừa là Truck. Ký hiệu thường là chữ "d" trong vòng tròn (notation Chen) hoặc cung loại trừ. - Overlapping / Inclusive (chồng lấn): Một instance có thể thuộc nhiều subtype cùng lúc. Ví dụ: trong một bệnh viện, một
Personvừa làDoctorvừa làPatient(bác sĩ cũng có thể khám bệnh tại chính bệnh viện mình). Ký hiệu là chữ "o" (overlapping).
Ba chiến lược chuyển subtype thành bảng vật lý
Trên ERD khái niệm, subtype/supertype rất đẹp và rõ ràng. Nhưng cơ sở dữ liệu quan hệ (như MySQL, PostgreSQL, SQL Server) không có khái niệm kế thừa. Vì vậy ta phải "ánh xạ" (mapping) nó thành bảng. Có ba cách chính:
Cách 1 — Single Table Inheritance (gộp tất cả vào một bảng): Một bảng duy nhất chứa toàn bộ thuộc tính của supertype lẫn mọi subtype, kèm một cột type để phân biệt.
- Ưu: Truy vấn đơn giản, không cần JOIN.
- Nhược: Rất nhiều cột NULL (cột của Car luôn rỗng với mọi dòng Motorcycle). Khó áp ràng buộc NOT NULL.
- Hợp khi: subtype ít thuộc tính riêng, hoặc cần đọc nhanh.
Vehicle chứa thuộc tính chung, mỗi subtype có bảng riêng (Car, Truck) dùng chung khóa chính, liên kết 1-1 với bảng cha.- Ưu: Chuẩn hóa tốt, không có cột thừa, ràng buộc rõ ràng.
- Nhược: Phải JOIN để lấy đủ thông tin một đối tượng.
- Hợp khi: mỗi subtype có nhiều thuộc tính riêng, dữ liệu cần sạch.
- Ưu: Mỗi bảng tự đủ, truy vấn từng loại nhanh.
- Nhược: Thuộc tính chung bị lặp lại; truy vấn "tất cả phương tiện" phải UNION nhiều bảng; khóa ngoại tham chiếu "một phương tiện bất kỳ" rất khó.
- Hợp khi: các subtype gần như độc lập, hiếm khi xử lý chung.
Tình huống thực tế
Tình huống 1 — Công ty bảo hiểm Bảo Việt: khách hàng cá nhân vs doanh nghiệp
Một nhóm BA xây hệ thống quản lý hợp đồng cho công ty bảo hiểm. Họ có supertype Customer với thuộc tính chung: customer_id, full_name, phone, address, email. Hai subtype:
IndividualCustomer:date_of_birth,national_id(CCCD),gender.CorporateCustomer:tax_code(MST),incorporation_date,legal_representative,business_field.
Đội chọn Cách 2 (bảng cha + bảng con): bảng customer giữ thông tin chung và một cột customer_type ENUM('individual','corporate'); hai bảng individual_customer và corporate_customer dùng customer_id làm khóa chính kiêm khóa ngoại trỏ về customer.
Diễn giải: Vì mỗi loại có 3-4 thuộc tính riêng quan trọng và đều cần ràng buộc NOT NULL (CCCD bắt buộc với cá nhân, MST bắt buộc với doanh nghiệp), gộp một bảng sẽ khiến không thể đặt NOT NULL được. Bảng hợp đồng policy chỉ cần trỏ khóa ngoại về customer — không quan tâm khách loại gì.
Bài học: Khi subtype có nhiều thuộc tính riêng bắt buộc, Class Table Inheritance bảo toàn được tính toàn vẹn dữ liệu mà Single Table không làm được.
Tình huống 2 — Sàn TMĐT giả định "ChợViệt": thanh toán đa phương thức
Một sàn thương mại điện tử kiểu Tiki/Shopee cần lưu các giao dịch thanh toán. Supertype Payment: payment_id, order_id, amount, paid_at, status. Các subtype theo phương thức:
CardPayment:card_last4,card_brand,gateway_ref.EWalletPayment:wallet_provider(Momo, ZaloPay, VNPay),wallet_account.CODPayment:shipper_id,collected_at.
Đội chọn Cách 1 (single table): một bảng payment với cột method và toàn bộ cột tùy chọn của ba loại, để NULL khi không áp dụng.
Diễn giải: Báo cáo tài chính của ChợViệt liên tục truy vấn "tổng tiền thanh toán trong ngày theo phương thức" — gộp một bảng giúp GROUP BY method cực nhanh, không JOIN. Số cột NULL chấp nhận được vì mỗi loại chỉ thêm 2-3 cột. Với 5 triệu giao dịch/ngày, tốc độ đọc quan trọng hơn sự gọn gàng về cấu trúc.
Bài học: Khi subtype nhẹ và truy vấn gộp là phổ biến, Single Table thắng về hiệu năng và sự đơn giản. Đừng máy móc luôn chuẩn hóa.
Tình huống 3 — Bệnh viện: overlapping subtype
Một bệnh viện tư ở TP.HCM mô hình hóa Person với hai subtype Doctor và Patient. Ban đầu đội định dùng Disjoint, nhưng nghiệp vụ phản hồi: chính các bác sĩ trong viện cũng đăng ký khám bệnh ở đây — vậy một người có thể vừa là Doctor vừa là Patient. Đây là Overlapping.
Vì overlapping, không thể dùng một cột type duy nhất. Đội chọn mô hình giống Cách 2 nhưng cho phép một person_id xuất hiện ở cả hai bảng con doctor và patient cùng lúc. Việc một người "là gì" được suy ra từ việc họ có dòng tương ứng trong bảng con nào.
Bài học: Overlapping subtype hầu như luôn cần các bảng con riêng (Cách 2), vì một cột type đơn lẻ không thể biểu diễn việc thuộc nhiều loại cùng lúc. Hỏi kỹ nghiệp vụ về disjointness trước khi chốt thiết kế — sai chỗ này phải đập đi xây lại.
Hướng dẫn từng bước
Khi gặp một nhóm thực thể nghi ngờ có quan hệ kế thừa, hãy theo quy trình sau:
- Kiểm tra phép thử "is-a". Viết câu "X là một Y". Nếu nghe tự nhiên (Ô tô là một Phương tiện) thì có thể là subtype. Nếu phải nói "X có một Y" hay "X dùng Y" thì không phải.
- Liệt kê thuộc tính chung và thuộc tính riêng. Lập bảng: cột nào áp dụng cho mọi loại (đưa lên supertype), cột nào chỉ áp dụng cho một loại (đưa xuống subtype). Nếu hầu hết thuộc tính đều chung, có thể không cần subtype.
- Chốt Completeness với nghiệp vụ. Hỏi: "Có instance nào không thuộc loại con nào đã liệt kê không?" → Total hay Partial.
- Chốt Disjointness với nghiệp vụ. Hỏi: "Một instance có thể thuộc nhiều loại cùng lúc không?" → Disjoint hay Overlapping.
- Vẽ trên ERD khái niệm. Dùng ký hiệu phù hợp với notation đang dùng (Chen có vòng tròn d/o; UML dùng tam giác kế thừa). Ghi rõ Total/Partial, Disjoint/Overlapping cạnh sơ đồ.
- Chọn chiến lược ánh xạ vật lý dựa trên: số lượng thuộc tính riêng, tần suất truy vấn gộp, yêu cầu ràng buộc NOT NULL, và disjointness. Bảng quyết định nhanh:
- Thêm cột phân loại và ràng buộc. Với Single/Class Table, thêm cột
type(ENUM hoặc khóa ngoại tới bảng tham chiếu loại). Cân nhắc CHECK constraint để đảm bảo cột của loại nào chỉ có giá trị khitypekhớp.
Lỗi thường gặp & mẹo
Lỗi 1 — Nhầm "is-a" với "has-a". Mô hình hóa Engine thành subtype của Car là sai — động cơ là thành phần của ô tô, không phải một loại ô tô. Luôn dùng phép thử is-a.
Lỗi 2 — Lạm dụng subtype cho biến thể nhỏ. Nếu hai "loại" chỉ khác nhau một cột, đừng tạo subtype. Một cột status hay category thường đủ. Subtype có chi phí (thêm bảng, thêm JOIN) — chỉ dùng khi sự khác biệt về cấu trúc đủ lớn.
Lỗi 3 — Quên hỏi disjointness, mặc định Disjoint. Như tình huống bệnh viện, mặc định sai dẫn tới thiết kế phải đập đi. Luôn hỏi rõ.
Lỗi 4 — Single Table với quá nhiều cột NULL. Khi mỗi subtype có 10+ cột riêng, single table tạo ra một bảng "rừng cột" với 80% ô trống. Vừa tốn dung lượng, vừa không đặt được NOT NULL, vừa khó đọc. Chuyển sang Class Table.
Lỗi 5 — Quên ràng buộc toàn vẹn trên cột type. Với single table, không gì ngăn một dòng method='cod' lại có card_last4 được điền. Dùng CHECK constraint để chặn dữ liệu vô nghĩa.
Mẹo: Trong tài liệu nghiệp vụ, hãy ghi rõ "Total/Partial, Disjoint/Overlapping" ngay cạnh mỗi cụm subtype. Đây là thông tin đội database cực kỳ cần và thường bị thiếu trong các bản ERD do BA bàn giao.
Mẹo: Nếu phân vân giữa các chiến lược, hãy hỏi: "Câu truy vấn nào chạy nhiều nhất?" Nếu hay truy vấn theo từng loại → nghiêng Class/Concrete. Nếu hay truy vấn gộp tất cả → nghiêng Single Table.
Bài tập thực hành
Bài 1 — Nhận diện: Cho danh sách sau, đánh dấu cặp nào là quan hệ subtype/supertype (is-a) và cặp nào không:
(a) Account — SavingsAccount, CheckingAccount; (b) Order — OrderItem; (c) Employee — FullTimeEmployee, Contractor; (d) Book — Author. Giải thích bằng phép thử is-a.
Bài 2 — Mô hình hóa: Một trường đại học có Person, gồm Student (mã sinh viên, khóa học, GPA) và Lecturer (mã giảng viên, học hàm, khoa). Trợ giảng (TA) vừa là sinh viên vừa giảng dạy. Hãy: (1) xác định Completeness và Disjointness; (2) chọn chiến lược ánh xạ vật lý và giải thích; (3) phác bảng.
Bài 3 — Chọn chiến lược: Một ngân hàng có Transaction với các loại Deposit, Withdrawal, Transfer, mỗi loại chỉ thêm 1-2 cột; hệ thống chạy báo cáo "tổng giao dịch theo loại theo ngày" hàng giờ. Bạn chọn chiến lược nào? Vì sao? Cân nhắc cả hiệu năng lẫn tính toàn vẹn.
Bài 4 — Phản biện: Một đồng nghiệp đề xuất gộp IndividualCustomer và CorporateCustomer (mỗi loại 5 cột riêng bắt buộc) vào một bảng single table. Hãy viết 3 lý do phản đối dựa trên tính toàn vẹn dữ liệu.
Tóm tắt
Subtype/Supertype là kỹ thuật mô hình hóa quan hệ "is-a": một entity cha tổng quát giữ thuộc tính chung, các entity con chuyên biệt giữ thuộc tính riêng. Trước khi thiết kế, BA bắt buộc chốt hai tính chất nghiệp vụ: Completeness (Total/Partial — instance có buộc thuộc một loại con không) và Disjointness (Disjoint/Overlapping — có thể thuộc nhiều loại cùng lúc không).
Vì CSDL quan hệ không hỗ trợ kế thừa, ta ánh xạ thành bảng theo ba chiến lược: Single Table (gộp một bảng, nhanh, hợp khi subtype nhẹ và hay truy vấn gộp), Class Table (bảng cha + bảng con, sạch, hợp khi subtype nhiều thuộc tính riêng bắt buộc hoặc overlapping), và Concrete Table (mỗi loại một bảng độc lập, hợp khi các loại ít liên quan nhau).
Ghi nhớ phép thử is-a để không nhầm với has-a, đừng lạm dụng subtype cho khác biệt nhỏ, luôn hỏi nghiệp vụ về disjointness, và đừng quên ràng buộc toàn vẹn trên cột phân loại. Nắm vững bài này, bạn sẽ chuyển hóa được những yêu cầu nghiệp vụ phức tạp ("khách có hai loại", "thanh toán nhiều cách") thành các sơ đồ vừa chính xác vừa dễ triển khai.