Bài 14 — Chuyển biểu đồ ER thành lược đồ quan hệ¶
🎯 Học xong bài này, bạn sẽ
- Thuộc thuật toán 7 bước chuyển biểu đồ ER thành các bảng
- Áp được từng bước lên biểu đồ ER của
truong_hocvà ra đúng 10 bảng - Biết vì sao 1:1 cần khoá ngoại +
UNIQUE, còn 1:N chỉ cần khoá ngoại - Biết chuyện gì xảy ra với thuộc tính đa trị, dẫn xuất, phức hợp khi lên bảng
- Chỉ ra được những ràng buộc mà ER diễn tả được nhưng bảng không giữ nổi
🧠 Câu chuyện mở đầu¶
Anh lập trình viên đã có tờ giấy vẽ biểu đồ ER, đã có cả bản Crow's Foot gọn gàng trên máy. Cô hiệu trưởng đã gật đầu: "Đúng rồi đấy."
Sáng thứ Hai, anh mở trình soạn thảo lên, gõ CREATE TABLE — rồi dừng lại.
Tờ giấy có bảy hình chữ nhật và bảy hình thoi. Vậy phải tạo bao nhiêu bảng? Bảy? Mười bốn?
Hình thoi CHỦ NHIỆM thì làm sao thành bảng được — nó có đúng hai đầu và chẳng có thuộc tính nào. Còn hình thoi PHÂN CÔNG DẠY nối vào ba hình chữ nhật thì nhét vào đâu?
Rồi cái elip đôi số điện thoại — một ô trong bảng chỉ chứa được một giá trị, mà học sinh thì có mấy số. Và elip nét đứt tuổi nữa, có cần cột tuoi không?
Anh nhận ra mình đang thiếu đúng một thứ: một bộ quy tắc máy móc, áp vào là ra kết quả, không phải đoán.
May thay, bộ quy tắc đó tồn tại, và nó chỉ có bảy bước.
📖 Khái niệm & thuật ngữ¶
Đây là bước chuyển giữa hai mức¶
Nhắc lại kiến trúc ba mức đã gặp ở Bài 3:
| Mức | Bản vẽ | Bài |
|---|---|---|
| Ý niệm | Biểu đồ ER / EER — thực thể, thuộc tính, mối quan hệ | Bài 7–10, 13 |
| Logic | Lược đồ quan hệ — bảng, cột, khoá chính, khoá ngoại | Bài này |
| Vật lý | File trên đĩa, chỉ mục, phân vùng | Cấp 4 |
Lược đồ quan hệ (relational schema) là danh sách các bảng kèm cột và ràng buộc của chúng — đúng thứ mà Bài 6 đã định nghĩa.
Thuật toán của bài này gọi là ánh xạ ER sang quan hệ (ER-to-relational mapping). Nó máy móc tới mức nhiều công cụ thiết kế database chạy tự động được.
Vì sao vẫn phải học tay nếu công cụ làm được?
Vì hai lẽ.
Thứ nhất, ba bước trong bảy bước có chỗ để bạn lựa chọn — và công cụ chọn hộ bạn thì thường chọn không tối ưu. Bước 3 (đặt khoá ngoại ở phía nào) và Bước 2 (khoá bộ phận hay khoá nhân tạo) là hai ví dụ mà bài này sẽ chỉ rõ.
Thứ hai, đọc ngược lại được — từ bảng suy ra biểu đồ ER — là kỹ năng dùng hằng ngày khi bạn nhận bàn giao một database lạ. Mà muốn đọc ngược thì phải thuộc chiều xuôi.
Thuật toán 7 bước¶
Đây là bảng tra để bạn quay lại nhiều lần. Từng bước sẽ có một mục riêng ở dưới.
| Bước | Trong biểu đồ ER | Thành gì trong lược đồ | Khoá chính của kết quả |
|---|---|---|---|
| 1 | Tập thực thể mạnh | Một bảng mới | Thuộc tính khoá của thực thể |
| 2 | Tập thực thể yếu | Một bảng mới + khoá ngoại về chủ | Khoá chủ + khoá bộ phận |
| 3 | Mối quan hệ 1:1 | Một cột khoá ngoại + UNIQUE, không tạo bảng |
Không đổi |
| 4 | Mối quan hệ 1:N | Một cột khoá ngoại ở phía N, không tạo bảng | Không đổi |
| 5 | Mối quan hệ M:N | Một bảng trung gian | Ghép khoá của cả hai phía |
| 6 | Thuộc tính đa trị | Một bảng riêng hai cột | Ghép khoá chủ + chính giá trị đó |
| 7 | Mối quan hệ bậc ≥ 3 | Một bảng riêng | Ghép khoá của mọi phía tham gia |
Hai quy tắc phụ, không cần bước riêng:
| Loại thuộc tính | Xử lý |
|---|---|
| Dẫn xuất | Không tạo cột. Tính lúc truy vấn |
| Phức hợp | Tạo cột cho từng thành phần con, hoặc gộp thành một cột nếu không bao giờ cần tra riêng |
Hai câu hỏi phân loại mọi mối quan hệ
Bốn bước 3, 4, 5, 7 chỉ là bốn nhánh của cùng một cây quyết định:
- Mối quan hệ này nối mấy thực thể? Từ ba trở lên → Bước 7, luôn luôn thành bảng.
- Nếu nối hai: bản số là gì? 1:1 → Bước 3 · 1:N → Bước 4 · M:N → Bước 5.
Và quy tắc bao trùm dễ nhớ nhất:
Chỉ M:N và bậc ≥ 3 mới sinh ra bảng mới. 1:1 và 1:N chỉ để lại một cột khoá ngoại.
Bước 1 — Thực thể mạnh thành bảng¶
Với mỗi tập thực thể mạnh, tạo một bảng. Lấy mọi thuộc tính đơn, đơn trị làm cột. Lấy thuộc tính khoá làm khoá chính.
Áp vào truong_hoc — có năm thực thể mạnh:
| Thực thể ER | Bảng | Khoá chính |
|---|---|---|
GIÁO VIÊN |
giao_vien |
ma_gv |
LỚP |
lop |
ma_lop |
HỌC SINH |
hoc_sinh |
ma_hs |
MÔN HỌC |
mon_hoc |
ma_mon |
SÁCH |
sach |
ma_sach |
Sau bước 1, hoc_sinh mới chỉ có (ma_hs, ho_ten, ngay_sinh, gioi_tinh, dia_chi). Chưa có ma_lop — cột đó là sản phẩm của Bước 4, không phải của Bước 1.
Đây là chỗ sai phổ biến nhất khi mới học
Nhìn bảng hoc_sinh trong dataset/02-chuan-hoa.sql thấy có ma_lop rồi tưởng nó là thuộc tính của HỌC SINH. Không phải.
Trong biểu đồ ER không tồn tại khoá ngoại (Bài 10 Lỗi 2). Mọi cột khoá ngoại bạn thấy trong lược đồ đều sinh ra ở bước 3, 4 hoặc 5 của thuật toán này.
Bước 2 — Thực thể yếu thành bảng¶
Với mỗi tập thực thể yếu, tạo một bảng gồm: thuộc tính riêng của nó, cộng khoá chính của thực thể chủ làm khoá ngoại.
Ba việc bắt buộc:
- Khoá chính = khoá chủ + khoá bộ phận.
- Cột khoá ngoại phải
NOT NULL— vì thực thể yếu luôn tham gia toàn phần (Bài 9). - Khai
ON DELETE CASCADE— vì thực thể yếu chết theo chủ.
truong_hoc có hai thực thể yếu, và chúng cho ra hai kết quả khác nhau một cách rất đáng học:
BUỔI ĐIỂM DANH |
PHỤ HUYNH |
|
|---|---|---|
| Thực thể chủ | HỌC SINH |
HỌC SINH |
| Khoá bộ phận theo lý thuyết | ngay |
quan_he |
| Khoá bộ phận có hợp lệ không | Có | Không — HS029 có hai dòng Bố |
| Khoá chính lý thuyết | (ma_hs, ngay) |
(ma_hs, quan_he) — không dùng được |
| Lược đồ thật làm gì | ma_dd SERIAL là khoá chính, giữ UNIQUE (ma_hs, ngay) |
ma_ph là khoá chính, không có ràng buộc thay thế |
ma_dd và ma_ph sinh ra ở ĐÂY, không có trong biểu đồ ER
Hai cột này là sản phẩm của Bước 2, đúng như Bài 9 và Bài 12 đã nói trước. Chúng là khoá nhân tạo (Bài 12), không phải thuộc tính của thực thể.
Người thiết kế được phép làm vậy, nhưng có một luật đi kèm:
Thay khoá tự nhiên bằng khoá nhân tạo thì phải giữ khoá tự nhiên lại bằng
UNIQUE— nếu khoá tự nhiên đó hợp lệ.
diem_danh tuân thủ: có UNIQUE (ma_hs, ngay). phu_huynh thì không cần, vì nó không có khoá tự nhiên hợp lệ nào để giữ. Hai trường hợp khác nhau, và phân biệt được chúng là dấu hiệu bạn đã hiểu bài.
Bước 3 — Quan hệ 1:1¶
Đây là bước mà Bài 8 và Bài 10 đã hẹn bạn tới.
Không tạo bảng mới. Chọn một trong hai bảng, thêm vào đó một cột khoá ngoại trỏ sang bảng kia, và đặt lên cột đó ràng buộc UNIQUE.
Công thức cần thuộc lòng:
1:1 = khoá ngoại +
UNIQUE1:N = khoá ngoại, KHÔNG có
UNIQUE
Đúng một chữ UNIQUE là toàn bộ khác biệt. Vì sao?
| Phần | Ép được điều gì |
|---|---|
| Cột khoá ngoại | Mỗi lớp trỏ tới tối đa một giáo viên — vì một ô chỉ chứa một giá trị |
UNIQUE trên cột đó |
Mỗi giáo viên xuất hiện ở tối đa một lớp — không ai ôm hai lớp |
Thiếu UNIQUE thì vế thứ hai mất, và quan hệ tụt xuống thành 1:N. Phần Thực hành sẽ chứng minh bằng một bảng nháp.
Đặt khoá ngoại ở phía nào? Đây là chỗ bạn thật sự phải chọn:
| Tình huống | Đặt ở đâu | Lý do |
|---|---|---|
| Một phía tham gia toàn phần | Phía toàn phần | Cột đó không bao giờ NULL |
| Cả hai phía bộ phận | Phía có ít NULL hơn |
Tiết kiệm chỗ, đọc dễ hơn |
| Cả hai phía toàn phần | Cân nhắc gộp làm một bảng | Hai thực thể luôn đi cùng nhau |
Trong truong_hoc, quan hệ GIÁO VIÊN — CHỦ NHIỆM — LỚP có cả hai phía đều bộ phận (lớp 9A3 chưa có chủ nhiệm; 3 thầy cô chưa chủ nhiệm lớp nào). Nên áp dòng thứ hai:
- Đặt ở
lop→ cộtma_gvcncó 1 ôNULL(lớp 9A3). - Đặt ở
giao_vien→ cộtma_lop_chu_nhiemsẽ có 3 ôNULL.
Chọn lop. Kết quả chính là dòng 48 của dataset/02-chuan-hoa.sql:
Ba mảnh, ba nhiệm vụ: REFERENCES ép "tối đa một giáo viên mỗi lớp", UNIQUE ép "tối đa một lớp mỗi giáo viên", và không có NOT NULL vì tham gia là bộ phận.
Vì sao ON DELETE SET NULL chứ không phải CASCADE?
Vì lớp học không phải thực thể yếu của giáo viên. Cô chủ nhiệm nghỉ hưu thì lớp 8A1 vẫn còn nguyên với 6 học sinh — chỉ là tạm thời chưa có chủ nhiệm.
SET NULL diễn đạt đúng điều đó: xoá giáo viên thì ô ma_gvcn trở về trống, lớp vẫn sống. Bài 15 sẽ so sánh đủ năm hành vi ON DELETE.
Bước 4 — Quan hệ 1:N¶
Không tạo bảng mới. Thêm một cột khoá ngoại vào bảng ở phía "nhiều", trỏ về khoá chính của phía "một". Không đặt UNIQUE.
Quy tắc ghi nhớ: khoá ngoại luôn nằm ở phía N.
Vì sao không làm ngược lại? Vì một ô chỉ chứa được một giá trị (Bài 6 — tính nguyên tử). Bảng lop không thể có cột danh_sach_hoc_sinh chứa cả một danh sách.
Áp vào truong_hoc:
| Mối quan hệ | Phía N | Cột sinh ra | NOT NULL? |
|---|---|---|---|
LỚP — HỌC TẠI — HỌC SINH |
hoc_sinh |
hoc_sinh.ma_lop |
Có — tham gia toàn phần |
HỌC SINH — LÀ PHỤ HUYNH CỦA — PHỤ HUYNH |
phu_huynh |
phu_huynh.ma_hs |
Có — quan hệ nhận diện |
HỌC SINH — ĐƯỢC ĐIỂM DANH — BUỔI ĐIỂM DANH |
diem_danh |
diem_danh.ma_hs |
Có — quan hệ nhận diện |
Hai dòng cuối trùng với Bước 2 — đúng vậy, vì quan hệ nhận diện của một thực thể yếu luôn là 1:N với phía N là thực thể yếu. Bước 2 và Bước 4 gặp nhau ở đó, và cho ra cùng một cột.
Còn NOT NULL thì lấy từ đâu? Từ ràng buộc tham gia đã ghi trong biểu đồ ER:
Tham gia toàn phần (đường đôi trong Chen, nửa
|trong Crow's Foot) ⇒ khoá ngoại khaiNOT NULL.
Bước 5 — Quan hệ M:N¶
Bắt buộc tạo bảng mới. Bảng trung gian gồm:
- Khoá chính của cả hai thực thể, làm hai cột khoá ngoại.
- Mọi thuộc tính của chính mối quan hệ (các elip treo vào hình thoi).
- Khoá chính = ghép hai cột khoá ngoại đó — trừ khi có lý do đổi.
Vì sao bắt buộc? Bài 8 đã chứng minh: đặt ma_mon vào hoc_sinh thì mỗi bạn chỉ có một môn; đặt ma_hs vào mon_hoc thì mỗi môn chỉ có một học sinh. Không có chỗ nào nhét nổi khoá ngoại.
truong_hoc có hai quan hệ M:N:
| Mối quan hệ | Bảng sinh ra | Hai khoá ngoại | Thuộc tính của mối quan hệ |
|---|---|---|---|
HỌC SINH — CÓ ĐIỂM — MÔN HỌC |
diem |
ma_hs, ma_mon |
hoc_ky, loai_diem, diem_so, ngay_nhap |
HỌC SINH — MƯỢN — SÁCH |
muon_sach |
ma_hs, ma_sach |
ngay_muon, ngay_tra_du_kien, ngay_tra_thuc_te |
Cả hai bảng này đều ĐỔI khoá chính so với quy tắc
Theo quy tắc, diem phải có khoá chính (ma_hs, ma_mon). Nhưng lược đồ thật dùng ma_diem SERIAL.
Lý do: cặp (ma_hs, ma_mon) không đủ — một học sinh có nhiều con điểm cùng môn (15 phút, 1 tiết, học kỳ). Thêm hoc_ky và loai_diem vào cũng vẫn chưa đủ, vì một học kỳ có thể có nhiều bài 15 phút cùng môn.
Đi tiếp theo hướng đó thì không bao giờ tới đích: không tồn tại tổ hợp cột nào của diem mà nghiệp vụ bảo đảm không trùng. Nói cách khác, diem không có khoá tự nhiên hợp lệ, nên khoá nhân tạo ma_diem là lựa chọn đúng, và việc lược đồ không có ràng buộc UNIQUE nào cũng là đúng.
Luật của Bước 2 có vế điều kiện "nếu khoá tự nhiên đó hợp lệ" — diem rơi vào vế không hợp lệ, y như muon_sach (một bạn được phép mượn lại cùng cuốn sách nhiều lần) và y như phu_huynh.
Bài 12 phân tích kỹ cả bốn trường hợp, và chỉ ra vì sao count(DISTINCT (ma_hs, ma_mon, hoc_ky, loai_diem)) = 480 trên dữ liệu mẫu không chứng minh được điều ngược lại.
Bước 6 — Thuộc tính đa trị¶
Bắt buộc tạo bảng mới, gồm đúng hai phần: khoá chính của thực thể chủ, và chính thuộc tính đa trị đó. Khoá chính của bảng mới là cả hai cột ghép lại.
Vì sao phải tách? Vì Bài 6 đã đặt luật: một ô chỉ chứa một giá trị. Ghi "0912345001, 0987000111" vào một ô là vi phạm tính nguyên tử, và mọi truy vấn về sau sẽ khổ sở.
Ví dụ chuẩn — thuộc tính đa trị so_dien_thoai của HỌC SINH (Bài 7 đã vẽ nó bằng elip đôi):
hoc_sinh_sdt(ma_hs, so_dien_thoai)
PRIMARY KEY (ma_hs, so_dien_thoai)
FOREIGN KEY (ma_hs) REFERENCES hoc_sinh ON DELETE CASCADE
Đúng hai cột, không hơn.
Bảng phu_huynh KHÔNG phải kết quả của Bước 6
Đây là nhầm lẫn nguy hiểm nhất của cả bài, và Bài 7 đã cảnh báo trước.
Rất dễ nhìn phu_huynh(ma_ph, ho_ten, so_dien_thoai, quan_he, ma_hs) rồi nghĩ "à, đây là số điện thoại đa trị đã được tách ra". Không phải.
| Bước 6 — thuộc tính đa trị | Bước 2 — thực thể yếu | |
|---|---|---|
| Bắt nguồn từ | Elip đôi treo vào HỌC SINH |
Hình chữ nhật đôi PHỤ HUYNH |
| Bảng sinh ra | hoc_sinh_sdt(ma_hs, so_dien_thoai) |
phu_huynh(ma_ph, ho_ten, so_dien_thoai, quan_he, ma_hs) |
| Số cột | Đúng 2 | 5 |
| Lưu được gì | Chỉ dãy số trơ | Số và tên, và quan hệ |
Phép thử để phân biệt, đã nêu ở Bài 7: thứ lặp lại mà chỉ là một giá trị trơ → thuộc tính đa trị (Bước 6). Thứ lặp lại mà có đặc điểm riêng cần mô tả → đó là một thực thể (Bước 1 hoặc Bước 2).
truong_hoc chọn hướng thực thể yếu, nên nó có bảng phu_huynh và không có bảng hoc_sinh_sdt. Phần Thực hành sẽ dựng thử hoc_sinh_sdt để bạn thấy nó khác thế nào.
Bước 7 — Quan hệ bậc từ ba trở lên¶
Bắt buộc tạo bảng mới, gồm khoá chính của mọi thực thể tham gia, cộng mọi thuộc tính của mối quan hệ. Khoá chính = ghép tất cả các khoá ngoại đó (thêm thuộc tính phân biệt nếu cần).
truong_hoc có một quan hệ bậc ba: PHÂN CÔNG DẠY nối GIÁO VIÊN, MÔN HỌC, LỚP, mang thuộc tính hoc_ky.
Kết quả:
Ba cột đầu là ba phía của hình thoi; cột thứ tư là thuộc tính treo trên hình thoi, và nó phải nằm trong khoá — nếu không, cô Lan dạy Toán cho 8A1 cả hai học kỳ sẽ thành hai dòng trùng nhau (Bài 12 đã phân tích).
Đây chính là khoá phức hợp bốn cột.
Hai quy tắc phụ: thuộc tính dẫn xuất và phức hợp¶
Thuộc tính dẫn xuất (elip nét đứt): không tạo cột. tuoi suy ra từ ngay_sinh, điểm trung bình suy ra từ bảng diem. Lưu lại chỉ tạo rủi ro lệch dữ liệu.
Thuộc tính phức hợp (elip nối elip con): có hai lựa chọn, và bạn phải quyết định dựa trên nghiệp vụ.
| Cách | Kết quả | Nên chọn khi |
|---|---|---|
| Tách thành các cột con | ho, ten_dem, ten |
Cần sắp xếp hoặc tìm kiếm theo từng phần |
| Gộp thành một cột | ho_ten |
Không bao giờ cần tra riêng |
truong_hoc chọn gộp — chỉ có một cột ho_ten VARCHAR(60). Đánh đổi: không sắp xếp được theo tên riêng, thứ mà danh bạ tiếng Việt hay cần. Đó là một quyết định thiết kế thật, không phải chuyện đúng sai.
Điều thuật toán KHÔNG làm được¶
Đây là phần thành thật mà Bài 9 đã hẹn. Biểu đồ ER diễn tả được nhiều ràng buộc hơn số ràng buộc mà bảng cưỡng chế nổi:
| Ràng buộc vẽ được trong ER | Bảng có giữ được không |
|---|---|
| Tham gia toàn phần ở phía 1 (mỗi học sinh một lớp) | Có — NOT NULL |
| Bản số 1:1 | Có — UNIQUE |
| Bản số 1 trên một nhánh của quan hệ bậc ba | Có — nhưng phải thêm UNIQUE bằng tay, xem hộp dưới |
| Tham gia toàn phần ở phía N (mỗi lớp ít nhất một học sinh) | Không — cần trigger |
| Ràng buộc giới hạn tổng (aggregate constraint) — ví dụ "một giáo viên không dạy quá 20 tiết mỗi tuần" | Không — cần cộng qua nhiều dòng, nhiều bảng |
| Chuyên biệt hoá toàn phần / disjoint (Bài 13) | Không — cần trigger hoặc mẹo khoá |
Khoảng cách này là cố hữu, không phải lỗi của ai. Cách xử lý đúng trong dự án thật: ghi rõ những ràng buộc không cưỡng chế được vào tài liệu và kiểm tra ở tầng ứng dụng, chứ đừng giả vờ là database đang lo hộ.
Dòng thứ ba là bài học đắt giá nhất của mục này
Giả sử trường ra quy định: "Mỗi lớp, mỗi môn, mỗi học kỳ chỉ do một giáo viên dạy." Trong biểu đồ ER, đó là bản số 1 trên nhánh GIÁO VIÊN của hình thoi bậc ba.
Nhìn vào khoá chính (ma_gv, ma_mon, ma_lop, hoc_ky) thì thấy nó không ép được điều đó: hai dòng (GV01, MH01, L01, 1) và (GV08, MH01, L01, 1) khác nhau ở ma_gv, nên khoá chính vui vẻ nhận cả hai.
Nhưng đừng vội kết luận "phải dùng trigger". SQL thuần làm được, chỉ cần thêm một ràng buộc nữa:
Đây là quy tắc sách vở: quan hệ bậc ba có bản số 1 ở một phía thì khoá chính là tập CÁC PHÍA CÒN LẠI. Phía GIÁO VIÊN có bản số 1, nên (ma_mon, ma_lop, hoc_ky) phải là khoá.
Điểm đắt giá: Bước 7 không tự sinh ra ràng buộc này. Thuật toán chỉ biết ghép mọi khoá lại thành khoá chính; nó không đọc bản số trên từng nhánh của hình thoi bậc ba. Người thiết kế phải quay lại biểu đồ ER và thêm tay.
(Lược đồ truong_hoc không khai ràng buộc này, vì quy định trên không nằm trong đặc tả của database mẫu. Đừng chạy câu ALTER ở trên trên database của khoá học — nó sẽ thành công và làm lệch lược đồ so với các bài sau.)
Bảng thuật ngữ¶
| Tiếng Việt | English | Nghĩa dễ hiểu |
|---|---|---|
| Lược đồ quan hệ | relational schema | Danh sách bảng, cột và ràng buộc |
| Ánh xạ ER sang quan hệ | ER-to-relational mapping | Thuật toán 7 bước của bài này |
| Bảng trung gian | junction / associative table | Bảng sinh ra để hiện thực M:N |
| Mức logic | logical level | Mức đã có bảng và cột, chưa nói tới lưu trữ vật lý |
🖼️ Sơ đồ¶
Sơ đồ 1 — Cây quyết định 7 bước¶
flowchart TB
START["Lấy một phần tử<br/>trong biểu đồ ER"]
Q1{"Phần tử này<br/>thuộc loại nào?"}
Q2{"Thực thể<br/>mạnh hay yếu?"}
B1["<b>BƯỚC 1</b><br/>Tạo bảng<br/>PK = thuộc tính khoá<br/>giao_vien · lop · hoc_sinh<br/>mon_hoc · sach"]
B2["<b>BƯỚC 2</b><br/>Tạo bảng + FK về chủ<br/>PK = khoá chủ + khoá bộ phận<br/>NOT NULL + ON DELETE CASCADE<br/>phu_huynh · diem_danh"]
Q3{"Mối quan hệ<br/>nối mấy<br/>thực thể?"}
B7["<b>BƯỚC 7</b><br/>Tạo bảng<br/>PK = ghép mọi khoá<br/>phan_cong_day"]
Q4{"Bản số<br/>là gì?"}
B3["<b>BƯỚC 3</b><br/>FK + UNIQUE<br/>KHÔNG tạo bảng<br/>lop.ma_gvcn"]
B4["<b>BƯỚC 4</b><br/>FK ở phía N<br/>KHÔNG tạo bảng<br/>hoc_sinh.ma_lop"]
B5["<b>BƯỚC 5</b><br/>Tạo bảng trung gian<br/>diem · muon_sach"]
Q5{"Là thuộc tính?"}
B6["<b>BƯỚC 6</b><br/>Nếu ĐA TRỊ thì tạo bảng 2 cột<br/>hoc_sinh_sdt"]
BX["Nếu DẪN XUẤT thì bỏ qua<br/>Nếu PHỨC HỢP thì tách cột con"]
START --> Q1
Q1 -->|"Thực thể"| Q2
Q2 -->|"Mạnh"| B1
Q2 -->|"Yếu"| B2
Q1 -->|"Mối quan hệ"| Q3
Q3 -->|"3 trở lên"| B7
Q3 -->|"2"| Q4
Q4 -->|"1:1"| B3
Q4 -->|"1:N"| B4
Q4 -->|"M:N"| B5
Q1 -->|"Thuộc tính"| Q5
Q5 -->|"Đa trị"| B6
Q5 -->|"Dẫn xuất hoặc phức hợp"| BX
style B1 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
style B2 fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px
style B3 fill:#fff8e1,stroke:#f9a825,stroke-width:3px
style B4 fill:#fff8e1,stroke:#f9a825,stroke-width:2px
style B5 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style B6 fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
style B7 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style BX fill:#eceff1,stroke:#607d8b
Năm bước sinh ra bảng mới: Bước 1, 2, 5, 6, 7. Hai ô vàng — Bước 3 và Bước 4 — chỉ sinh ra một cột khoá ngoại. Ô xám cuối cùng thì không sinh ra gì cả. Đó là toàn bộ thuật toán gói trong một hình.
Sơ đồ 2 — Từ biểu đồ ER tới đúng 10 bảng¶
Sơ đồ này ghép sơ đồ Chen của Bài 10 với kết quả sau chuyển đổi:
flowchart LR
subgraph ER["BIỂU ĐỒ ER — 7 chữ nhật + 7 thoi"]
E1["GIÁO VIÊN"]
E2["LỚP"]
E3["HỌC SINH"]
E4["MÔN HỌC"]
E5["SÁCH"]
E6[["PHỤ HUYNH"]]
E7[["BUỔI ĐIỂM DANH"]]
R1{"CHỦ NHIỆM 1-1"}
R2{"HỌC TẠI 1-N"}
R3{"LÀ PH CỦA 1-N"}
R4{"ĐƯỢC ĐIỂM DANH 1-N"}
R5{"CÓ ĐIỂM M-N"}
R6{"MƯỢN M-N"}
R7{"PHÂN CÔNG DẠY bậc 3"}
end
subgraph LD["LƯỢC ĐỒ QUAN HỆ — đúng 10 bảng"]
T1["giao_vien"]
T2["lop<br/>+ cột ma_gvcn UNIQUE"]
T3["hoc_sinh<br/>+ cột ma_lop NOT NULL"]
T4["mon_hoc"]
T5["sach"]
T6["phu_huynh"]
T7["diem_danh"]
T8["diem"]
T9["muon_sach"]
T10["phan_cong_day"]
end
E1 -->|"Bước 1"| T1
E2 -->|"Bước 1"| T2
E3 -->|"Bước 1"| T3
E4 -->|"Bước 1"| T4
E5 -->|"Bước 1"| T5
E6 -->|"Bước 2"| T6
E7 -->|"Bước 2"| T7
R1 -.->|"Bước 3 · chỉ 1 cột"| T2
R2 -.->|"Bước 4 · chỉ 1 cột"| T3
R3 -.->|"Bước 4 · chỉ 1 cột"| T6
R4 -.->|"Bước 4 · chỉ 1 cột"| T7
R5 -->|"Bước 5"| T8
R6 -->|"Bước 5"| T9
R7 -->|"Bước 7"| T10
style E6 fill:#f3e5f5,stroke:#6a1b9a,stroke-width:3px
style E7 fill:#f3e5f5,stroke:#6a1b9a,stroke-width:3px
style T2 fill:#fff8e1,stroke:#f9a825,stroke-width:3px
style T3 fill:#fff8e1,stroke:#f9a825,stroke-width:2px
Đọc sơ đồ theo loại mũi tên:
| Mũi tên | Nghĩa | Số lượng |
|---|---|---|
| Nét liền | Phần tử ER này sinh ra một bảng | 10 |
| Nét đứt | Phần tử ER này chỉ thêm một cột vào bảng đã có | 4 |
Bảy hình chữ nhật + ba hình thoi (M:N và bậc ba) = 10 bảng. Bốn hình thoi còn lại (1:1 và 1:N) = 4 cột khoá ngoại. Đó là toàn bộ dataset/02-chuan-hoa.sql.
💻 Thực hành¶
Kiểm chứng kết quả: đúng 10 bảng¶
SELECT table_name,
(SELECT count(*) FROM information_schema.columns c
WHERE c.table_schema = 'public' AND c.table_name = t.table_name) AS so_cot
FROM information_schema.tables t
WHERE table_schema = 'public'
AND table_name IN ('giao_vien', 'lop', 'hoc_sinh', 'phu_huynh', 'mon_hoc',
'phan_cong_day', 'diem', 'sach', 'muon_sach', 'diem_danh')
ORDER BY table_name;
Mười dòng, đúng bằng con số mà Sơ đồ 2 dự đoán.
Và bốn cột khoá ngoại sinh ra từ Bước 3 và Bước 4:
SELECT c.conrelid::regclass AS bang, a.attname AS cot_sinh_ra,
c.confrelid::regclass AS tro_ve_bang
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey)
WHERE c.contype = 'f'
AND c.conrelid::regclass::text IN ('lop', 'hoc_sinh', 'phu_huynh', 'diem_danh')
ORDER BY c.conrelid::regclass::text;
Bốn dòng: diem_danh.ma_hs, hoc_sinh.ma_lop, lop.ma_gvcn, phu_huynh.ma_hs. Bốn hình thoi nét đứt trong Sơ đồ 2, không thêm không bớt.
Bước 3 — chứng minh UNIQUE là thứ làm nên 1:1¶
Dựng một bảng nháp cố ý thiếu UNIQUE, để thấy quan hệ tụt xuống 1:N:
DROP TABLE IF EXISTS b14_lop_thieu_unique CASCADE;
CREATE TABLE b14_lop_thieu_unique (
ma_lop CHAR(3) PRIMARY KEY,
ten_lop VARCHAR(10) NOT NULL,
ma_gvcn CHAR(4) REFERENCES giao_vien(ma_gv)
);
INSERT INTO b14_lop_thieu_unique VALUES
('X01', 'Thử 1', 'GV01'),
('X02', 'Thử 2', 'GV01');
Hai dòng chèn thành công. Cô Lan (GV01) giờ chủ nhiệm hai lớp:
Kết quả: GV01 | 2.
Thiếu một chữ UNIQUE, quan hệ 1:1 đã im lặng biến thành 1:N. Không có lỗi nào, không có cảnh báo nào — đó mới là chỗ nguy hiểm.
Bây giờ thử đúng việc đó trên bảng thật, nơi có lop_ma_gvcn_key:
PostgreSQL 16 từ chối:
ERROR: duplicate key value violates unique constraint "lop_ma_gvcn_key"
DETAIL: Key (ma_gvcn)=(GV01) already exists.
Hai bảng, cùng một câu lệnh, hai kết quả khác hẳn nhau. Toàn bộ khác biệt nằm ở một chữ UNIQUE — đúng như Bước 3 đã nói.
Bước 6 — dựng thử bảng thuộc tính đa trị¶
truong_hoc không có bảng hoc_sinh_sdt, vì nó chọn hướng thực thể yếu. Nhưng ta dựng thử để thấy Bước 6 cho ra cái gì. Bảng nháp mang tiền tố b14_ và sẽ được xoá ở cuối mục — lược đồ thật không có bảng này:
DROP TABLE IF EXISTS b14_hoc_sinh_sdt CASCADE;
CREATE TABLE b14_hoc_sinh_sdt (
ma_hs CHAR(5) NOT NULL REFERENCES hoc_sinh(ma_hs) ON DELETE CASCADE,
so_dien_thoai VARCHAR(15) NOT NULL,
PRIMARY KEY (ma_hs, so_dien_thoai)
);
INSERT INTO b14_hoc_sinh_sdt VALUES
('HS001', '0912345001'),
('HS001', '0987000111'),
('HS002', '0912345003');
Đúng hai cột (ngoài tiền tố b14_ chỉ dùng cho bảng nháp), khoá chính là cả hai cột ghép lại — đúng công thức "khoá chủ + chính giá trị đó".
SELECT ma_hs, count(*) AS so_dien_thoai_da_khai
FROM b14_hoc_sinh_sdt
GROUP BY ma_hs
ORDER BY ma_hs;
HS001 có 2 số, HS002 có 1 số. Một học sinh nhiều số điện thoại — đúng nghĩa đa trị, mà vẫn giữ được một ô một giá trị.
Bây giờ so sánh với bảng phu_huynh thật:
SELECT ma_hs, count(*) AS so_phu_huynh,
string_agg(quan_he, ', ' ORDER BY ma_ph) AS cac_quan_he
FROM phu_huynh
WHERE ma_hs IN ('HS001', 'HS029')
GROUP BY ma_hs
ORDER BY ma_hs;
HS001 có 2 phụ huynh (Bố, Mẹ), HS029 cũng có 2 (Bố, Bố). Bảng này lưu được tên và quan hệ, thứ mà hoc_sinh_sdt hai cột không lưu nổi.
Đó chính là ranh giới giữa Bước 6 và Bước 2: cùng là "nhiều giá trị cho một học sinh", nhưng một bên là giá trị trơ, một bên là thực thể có đặc điểm riêng.
Một điều nữa đáng để ý: b14_hoc_sinh_sdt không có cột khoá nhân tạo nào, vì tổ hợp (ma_hs, so_dien_thoai) là khoá tự nhiên hợp lệ — một học sinh không thể khai cùng một số điện thoại hai lần. Đây là trường hợp ngược với bảng diem mà Bài 12 phân tích.
Bước 2 — hai thực thể yếu, hai kết quả khác nhau¶
SELECT conrelid::regclass AS bang, conname, pg_get_constraintdef(oid) AS dinh_nghia
FROM pg_constraint
WHERE conrelid IN ('phu_huynh'::regclass, 'diem_danh'::regclass)
AND contype IN ('p', 'u', 'f')
ORDER BY conrelid::regclass::text, conname;
Đọc kết quả:
| Bảng | Có gì | Ý nghĩa theo Bước 2 |
|---|---|---|
diem_danh |
diem_danh_pkey trên ma_dd và diem_danh_ma_hs_ngay_key |
Khoá nhân tạo, nhưng khoá tự nhiên được giữ |
phu_huynh |
Chỉ phu_huynh_pkey trên ma_ph |
Khoá nhân tạo, không có khoá tự nhiên hợp lệ để giữ |
| Cả hai | Khoá ngoại ma_hs với ON DELETE CASCADE |
Chết theo chủ — đặc trưng của thực thể yếu |
Kiểm chứng rằng cả hai khoá ngoại đều NOT NULL và đều CASCADE:
SELECT c.conrelid::regclass AS bang, a.attname AS cot,
a.attnotnull AS bat_buoc, c.confdeltype AS hanh_vi_xoa
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey)
WHERE c.contype = 'f'
AND c.conrelid IN ('phu_huynh'::regclass, 'diem_danh'::regclass)
ORDER BY c.conrelid::regclass::text;
Cả hai dòng: bat_buoc = t, hanh_vi_xoa = c (viết tắt của cascade). Đúng hai điều mà Bước 2 bắt buộc.
Bước 7 — khoá chính bốn cột¶
SELECT a.attname AS cot, a.attnum AS thu_tu
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey)
WHERE c.conrelid = 'phan_cong_day'::regclass AND c.contype = 'p'
ORDER BY a.attnum;
Bốn dòng: ma_gv, ma_mon, ma_lop, hoc_ky. Ba khoá ngoại của ba phía, cộng một thuộc tính của mối quan hệ.
Và bảng này thật sự chứa các bộ ba khác nhau:
SELECT count(*) AS tong_so_dong,
count(DISTINCT ma_gv) AS so_giao_vien,
count(DISTINCT ma_mon) AS so_mon,
count(DISTINCT ma_lop) AS so_lop,
count(DISTINCT (ma_gv, ma_mon, ma_lop)) AS so_bo_ba
FROM phan_cong_day;
Kết quả: 64, 8, 8, 4, 32. Ba mươi hai bộ ba khác nhau, mỗi bộ xuất hiện ở hai học kỳ → 64 dòng. Đúng cấu trúc mà Bước 7 dự đoán.
Quy tắc phụ — thuộc tính dẫn xuất không có cột¶
SELECT count(*) AS so_cot_ten_tuoi
FROM information_schema.columns
WHERE table_schema = 'public' AND column_name IN ('tuoi', 'diem_trung_binh');
Kết quả 0. Cả tuoi lẫn diem_trung_binh đều không tồn tại — đúng như quy tắc.
Và chúng được tính khi cần:
SELECT ho_ten,
date_part('year', age(CURRENT_DATE, ngay_sinh))::int AS tuoi
FROM hoc_sinh
ORDER BY ma_hs
LIMIT 3;
Ba dòng đầu của bảng hoc_sinh kèm tuổi tính tại thời điểm chạy. Không lưu, nên không bao giờ sai.
⚠️ Lỗi thường gặp¶
Lỗi 1: Tạo bảng cho mối quan hệ 1:N
Thấy hình thoi HỌC TẠI nên tạo bảng hoc_tai(ma_hs, ma_lop).
Thừa hoàn toàn. Vì mỗi học sinh chỉ có một lớp, bảng đó sẽ có đúng 40 dòng — bằng số học sinh — và mọi truy vấn phải JOIN thêm một lần vô ích.
Chỉ M:N và bậc ≥ 3 mới sinh bảng. 1:1 và 1:N chỉ để lại một cột.
Lỗi 2: Quên UNIQUE khi chuyển quan hệ 1:1
Đây là lỗi tinh vi nhất, vì không có thông báo lỗi nào cả. Bảng vẫn tạo được, dữ liệu vẫn chèn được, mọi thứ trông bình thường.
Chỉ tới một ngày nào đó bạn chạy truy vấn thống kê và thấy cô Lan đang chủ nhiệm ba lớp. Phần Thực hành đã dựng đúng tình huống đó bằng bảng b14_lop_thieu_unique.
Chuyển 1:1 mà không gõ chữ UNIQUE thì bạn vừa chuyển thành 1:N.
Lỗi 3: Đặt khoá ngoại nhầm phía trong quan hệ 1:N
Thêm cột lop.ma_hs để ghi "lớp này có học sinh nào".
Một ô chỉ chứa một giá trị, nên cột đó chỉ ghi được một học sinh mỗi lớp. Sai hoàn toàn về bản số.
Khoá ngoại luôn nằm ở phía N — nơi có nhiều dòng, mỗi dòng trỏ về một dòng phía 1.
Lỗi 4: Nhầm bảng thực thể yếu với bảng thuộc tính đa trị
Nhìn phu_huynh rồi gọi nó là "bảng tách ra từ thuộc tính đa trị số điện thoại".
Sai, và nhầm này kéo theo hàng loạt câu trả lời sai về sau. Phép thử nhanh: bảng của Bước 6 có đúng hai cột. phu_huynh có năm cột, trong đó ho_ten và quan_he là đặc điểm riêng của một thực thể — nên nó là kết quả của Bước 2.
Lỗi 5: Tạo cột cho thuộc tính dẫn xuất
Thêm cột tuoi INTEGER vào hoc_sinh cho tiện.
Sang năm, mọi học sinh già đi một tuổi mà cột đó vẫn giữ nguyên. Bạn phải nhớ chạy một câu UPDATE mỗi năm, và chỉ cần quên một lần là dữ liệu sai vĩnh viễn.
Thứ tính được thì tính, đừng lưu. (Có một ngoại lệ hợp lệ: khi tính quá chậm, người ta cố ý lưu lại để tăng tốc — Cấp 4 sẽ bàn, và khi đó phải có cơ chế cập nhật tự động.)
Lỗi 6: Tin rằng lược đồ đã giữ mọi ràng buộc của biểu đồ ER
Chuyển xong 7 bước rồi kết luận "xong, database đảm bảo mọi thứ rồi".
Không đúng. Ba loại ràng buộc không đi qua nổi bước chuyển đổi: tham gia toàn phần ở phía N, ràng buộc giới hạn tổng, và ràng buộc chuyên biệt hoá.
Và có một loại thứ tư còn nguy hiểm hơn, vì nó giữ được mà thuật toán lại không tự sinh ra: bản số 1 trên một nhánh của quan hệ bậc ba, cần một UNIQUE thêm tay.
Việc cần làm là ghi lại chúng vào tài liệu thiết kế và kiểm tra ở tầng ứng dụng — chứ không phải giả vờ chúng không tồn tại.
✍️ Bài tập¶
-
Với mỗi phần tử ER sau, cho biết áp bước nào và kết quả là gì:
a. Tập thực thể
SÁCHvới thuộc tính khoáma_sach. b. Mối quan hệHỌC SINH — MƯỢN — SÁCH, bản số M:N, có thuộc tínhngay_muon. c. Thuộc tính đa trịemail_lien_hecủaGIÁO VIÊN. d. Mối quan hệTRƯỜNG — CÓ — HIỆU TRƯỞNG, bản số 1:1, cả hai phía toàn phần. e. Thuộc tính dẫn xuấtso_sach_dang_muoncủaHỌC SINH. -
Trường mở hệ thống phòng học và thiết bị (đã gặp ở Bài 9 Bài tập 2): thiết bị chỉ được đánh số trong phạm vi từng phòng. Hãy áp thuật toán và viết ra lược đồ đầy đủ, kèm mọi ràng buộc.
-
Một quan hệ bậc ba
BÁC SĨ — KHÁM — BỆNH NHÂN — tại — PHÒNG KHÁMvới thuộc tínhthoi_diem. Hãy viết bảng kết quả. Khoá chính gồm những cột nào và vì sao? -
Viết một câu SQL chứng minh rằng mọi bảng trong
truong_hocđều có đúng một khoá chính, và cho biết bảng nào có khoá chính nhiều hơn một cột. -
Một bạn chuyển biểu đồ ER sau sang bảng:
CÂU LẠC BỘ(ma_clb, ten_clb) — THAM GIA (M:N, thuộc tínhngay_vao) —HỌC SINH(ma_hs, ho_ten)Bạn ấy viết:
Chỉ ra lỗi và viết lại cho đúng.
Đáp án
Câu 1.
| Bước | Kết quả | |
|---|---|---|
| a | Bước 1 | sach(ma_sach PK, ten_sach, tac_gia, ...) |
| b | Bước 5 | muon_sach(ma_hs FK, ma_sach FK, ngay_muon, ...) — bảng trung gian mới |
| c | Bước 6 | giao_vien_email(ma_gv, email_lien_he) — đúng 2 cột, PRIMARY KEY (ma_gv, email_lien_he) |
| d | Bước 3 | Một cột khoá ngoại + UNIQUE. Vì cả hai phía toàn phần, cột đó cũng phải NOT NULL. Thậm chí nên cân nhắc gộp hai thực thể làm một bảng — chúng luôn đi cùng nhau |
| e | Quy tắc phụ | Không tạo cột. Tính bằng count(*) trên muon_sach với ngay_tra_thuc_te IS NULL |
Câu 2.
THIẾT BỊ là thực thể yếu, chủ là PHÒNG HỌC, khoá bộ phận là so_thu_tu. Áp Bước 1 rồi Bước 2:
phong_hoc(ma_phong, ten_phong, suc_chua)
PRIMARY KEY (ma_phong)
thiet_bi(ma_phong, so_thu_tu, ten_thiet_bi, tinh_trang)
PRIMARY KEY (ma_phong, so_thu_tu)
FOREIGN KEY (ma_phong) REFERENCES phong_hoc(ma_phong) ON DELETE CASCADE
ma_phong NOT NULL
Ba điều bắt buộc của Bước 2 đều có mặt: khoá chính ghép khoá chủ + khoá bộ phận, khoá ngoại NOT NULL, và ON DELETE CASCADE.
Ở đây khoá bộ phận so_thu_tu hợp lệ (giống diem_danh, khác phu_huynh), nên không cần khoá nhân tạo.
Câu 3. Áp Bước 7:
kham_benh(ma_bac_si, ma_benh_nhan, ma_phong_kham, thoi_diem)
PRIMARY KEY (ma_bac_si, ma_benh_nhan, ma_phong_kham, thoi_diem)
Khoá chính gồm cả bốn cột. Ba cột đầu là ba phía của quan hệ bậc ba. Cột thoi_diem phải nằm trong khoá, vì cùng một bác sĩ có thể khám cùng một bệnh nhân tại cùng phòng khám nhiều lần — thiếu thoi_diem thì các lần khám đó trùng nhau.
Đây đúng vai trò mà hoc_ky đóng trong phan_cong_day.
Lưu ý thực tế: nếu thoi_diem chỉ chính xác tới ngày thì vẫn hỏng (khám hai lần trong ngày). Lúc đó nên chuyển sang khoá nhân tạo ma_lan_kham, và nhớ luật của Bước 2: giữ khoá tự nhiên bằng UNIQUE nếu nó hợp lệ.
Câu 4.
SELECT c.conrelid::regclass AS bang,
count(*) AS so_cot_trong_khoa_chinh
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey)
WHERE c.contype = 'p'
AND c.conrelid::regclass::text IN
('giao_vien', 'lop', 'hoc_sinh', 'phu_huynh', 'mon_hoc',
'phan_cong_day', 'diem', 'sach', 'muon_sach', 'diem_danh')
GROUP BY 1
ORDER BY 2 DESC, 1;
Kết quả 10 dòng — mỗi bảng đúng một khoá chính (nếu có bảng nào hai khoá chính thì nó sẽ xuất hiện hai lần, mà PostgreSQL không cho phép điều đó).
Chỉ một bảng có khoá chính nhiều hơn một cột: phan_cong_day với 4 cột. Chín bảng còn lại đều một cột. Đó là dấu hiệu rõ nhất cho thấy phan_cong_day là sản phẩm của Bước 7.
Câu 5. Lỗi: bạn ấy nhét khoá ngoại vào phía 1 của một quan hệ M:N, tức là áp nhầm Bước 4 cho một trường hợp phải dùng Bước 5.
Hậu quả cụ thể: bảng cau_lac_bo chỉ ghi được một học sinh cho mỗi câu lạc bộ. Mà nếu muốn ghi nhiều học sinh thì phải lặp lại ten_clb ở nhiều dòng, làm hỏng luôn khoá chính ma_clb.
Viết lại theo Bước 5:
cau_lac_bo(ma_clb, ten_clb)
PRIMARY KEY (ma_clb)
hoc_sinh(ma_hs, ho_ten)
PRIMARY KEY (ma_hs)
tham_gia(ma_clb, ma_hs, ngay_vao)
PRIMARY KEY (ma_clb, ma_hs)
FOREIGN KEY (ma_clb) REFERENCES cau_lac_bo(ma_clb)
FOREIGN KEY (ma_hs) REFERENCES hoc_sinh(ma_hs)
Chú ý ngay_vao nằm trong bảng trung gian, không nằm ở cau_lac_bo cũng không ở hoc_sinh — vì nó là thuộc tính của mối quan hệ.
🔑 Tóm tắt¶
- Thuật toán 7 bước chuyển biểu đồ ER thành lược đồ quan hệ, và quy tắc bao trùm là: chỉ M:N (Bước 5) và bậc ≥ 3 (Bước 7) mới sinh ra bảng mới; 1:1 (Bước 3) và 1:N (Bước 4) chỉ để lại một cột khoá ngoại.
- 1:1 = khoá ngoại +
UNIQUE; 1:N = khoá ngoại, khôngUNIQUE. Đúng một chữ đó là toàn bộ khác biệt, và quên nó thì không có lỗi nào báo cho bạn biết. - Thực thể yếu (Bước 2) cho ra bảng với khoá chính khoá chủ + khoá bộ phận, khoá ngoại
NOT NULLvàON DELETE CASCADE; khi khoá bộ phận không hợp lệ thì dùng khoá nhân tạo —ma_phvàma_ddsinh ra đúng ở bước này. - Thuộc tính đa trị (Bước 6) thành một bảng hai cột, khác hẳn bảng của thực thể yếu; dẫn xuất thì không tạo cột; phức hợp thì tách hoặc gộp tuỳ nghiệp vụ.
- Áp bảy bước lên biểu đồ ER của
truong_hoccho ra đúng 10 bảng và 4 cột khoá ngoại nhúng củadataset/02-chuan-hoa.sql— nhưng vẫn còn ba loại ràng buộc mà bảng không giữ nổi, phải ghi vào tài liệu.