Bài 11 — Biểu đồ ER — ký hiệu Crow's Foot và vẽ bằng Mermaid¶
🎯 Học xong bài này, bạn sẽ
- Đọc được ký hiệu Crow's Foot — bốn ký hiệu đầu đường và ý nghĩa của từng nửa
- Đối chiếu được Chen ↔ Crow's Foot, và biết Crow's Foot bỏ mất những gì
- Tự viết được một sơ đồ
erDiagrambằng Mermaid, kể cả phần khai báo cột - Đọc được biểu đồ ER hoàn chỉnh của
truong_hoc— sơ đồ tham chiếu cho mọi bài sau - Suy ra được ký hiệu đúng của một đường nối chỉ bằng cách soi
NOT NULLvàUNIQUE
🧠 Câu chuyện mở đầu¶
Anh lập trình viên ở Bài 10 về nhà, mở máy tính lên và định gõ lại tờ giấy vừa vẽ với cô hiệu trưởng.
Rồi anh khựng lại.
Trên giấy, mỗi tập thực thể là một hình chữ nhật với cả chục elip treo lủng lẳng xung quanh. HỌC SINH có sáu elip, GIÁO VIÊN có bảy, ĐIỂM có bảy nữa. Cộng lại hơn năm mươi hình. Vẽ tay trên giấy A3 thì còn được, chứ nhét vừa một màn hình thì không.
Mà anh còn cần thứ khác nữa. Sang tuần anh phải viết CREATE TABLE. Lúc đó anh cần biết mỗi bảng có cột gì, cột nào là khoá — chứ không cần biết ho_ten có tách được thành họ và tên hay không.
Anh mở một công cụ vẽ sơ đồ database. Giao diện hiện ra khác hẳn: mỗi bảng là một hình chữ nhật có danh sách cột bên trong, không còn elip nào. Và ở hai đầu mỗi đường nối là những ký hiệu lạ — một cái trông đúng như bàn chân con quạ.
Cùng một database, hai cách vẽ hoàn toàn khác nhau. Vậy cách vẽ thứ hai này là gì, và nó đánh đổi cái gì để lấy sự gọn gàng đó?
📖 Khái niệm & thuật ngữ¶
Crow's Foot là gì¶
Ký hiệu Crow's Foot (Crow's Foot notation, còn gọi là IE notation — Information Engineering) là bộ ký hiệu vẽ biểu đồ ER do Gordon Everest đề xuất năm 1976, được đặt tên theo hình bàn chân quạ — ba nhánh toẽ ra — dùng để chỉ phía "nhiều".
Hai khác biệt lớn nhất so với ký hiệu Chen:
| Chen | Crow's Foot | |
|---|---|---|
| Thuộc tính vẽ ở đâu | Mỗi thuộc tính một elip treo ra ngoài | Liệt kê bên trong hình chữ nhật |
| Bản số và tham gia ghi ở đâu | Chữ 1/N trên đường, đường đơn/đôi |
Một ký hiệu ghép ở mỗi đầu đường |
Đổi lại sự gọn gàng, Crow's Foot mất ba thứ mà Chen diễn tả được. Chúng ta sẽ quay lại chỗ mất mát này ở cuối phần khái niệm — đó là lý do khoá học dạy Chen trước.
Ký hiệu Crow's Foot gồm hai nửa¶
Đây là ý quan trọng nhất của cả bài, và cũng là chỗ người học hay đọc sai.
Mỗi đầu đường nối là hai ký tự ghép lại, và mỗi ký tự trả lời một câu hỏi khác nhau:
| Nửa | Nằm ở đâu | Trả lời câu hỏi | Bài đã học |
|---|---|---|---|
| Nửa trong | ký tự sát thực thể | Tối đa bao nhiêu? → bản số | Bài 8 |
| Nửa ngoài | ký tự xa thực thể hơn, phía đường nối | Tối thiểu bao nhiêu? → ràng buộc tham gia | Bài 9 |
Đúng hai câu hỏi mà Bài 9 đã dạy: giới hạn trên và giới hạn dưới. Crow's Foot chỉ làm một việc: gói cả hai vào một ký hiệu duy nhất.
Xem tận mắt trên một ví dụ. Lấy giao_vien |o--o| lop và tách từng ký tự ra:
giao_vien | o -- o | lop
│ │ │ │
│ │ │ └── sát lop → nửa TRONG → tối đa 1 lớp
│ │ └───── xa lop hơn → nửa NGOÀI → tối thiểu 0
│ └──────────── xa GV hơn → nửa NGOÀI → tối thiểu 0
└─────────────── sát GV → nửa TRONG → tối đa 1 giáo viên
Nhìn theo hình: ký tự chạm vào hình chữ nhật cho biết "nhiều nhất bao nhiêu", ký tự lùi vào trong đường nối cho biết "ít nhất bao nhiêu".
Bảng tra bốn ký hiệu, viết theo cú pháp Mermaid:
Mermaid viết cùng một ký hiệu theo hai chiều, tuỳ nó nằm bên trái hay bên phải dấu --. Hai cột đầu của bảng dưới đây là cùng một hình vẽ, chỉ lật gương:
| Viết ở bên trái | Viết ở bên phải | Nửa trong (sát thực thể) → tối đa | Nửa ngoài (phía đường) → tối thiểu | Đọc là |
|---|---|---|---|---|
|| |
|| |
vạch | = tối đa 1 |
vạch | = tối thiểu 1 |
đúng một |
|o |
o| |
vạch | = tối đa 1 |
vòng tròn o = tối thiểu 0 |
không hoặc một |
}| |
|{ |
chân quạ } { = tối đa nhiều |
vạch | = tối thiểu 1 |
một hoặc nhiều |
}o |
o{ |
chân quạ } { = tối đa nhiều |
vòng tròn o = tối thiểu 0 |
không hoặc nhiều |
Mẹo gõ cho khỏi sai: chân quạ luôn toẽ về phía hình chữ nhật, nên nó là } khi nằm bên trái và { khi nằm bên phải.
Mẹo nhớ gọn trong hai dòng:
Vòng tròn
o= con số 0. Thấy vòng tròn là "được phép không có".Chân quạ
}= nhiều. Thấy chân quạ toẽ ra là "được phép có nhiều".
Ký hiệu ở đầu nào thì mô tả thực thể ở đầu ĐÓ
Viết lop ||--o{ hoc_sinh thì:
- Ký hiệu
||nằm sátlop→ nói vềlop: mỗi học sinh thuộc đúng một lớp. - Ký hiệu
o{nằm sáthoc_sinh→ nói vềhoc_sinh: mỗi lớp có không hoặc nhiều học sinh.
Rất nhiều người đọc ngược, rồi kết luận sai hoàn toàn về lược đồ. Cách đọc đúng luôn là: "mỗi thực thể ở đầu kia ứng với bao nhiêu thực thể ở đầu này?"
Trong Mermaid, chân quạ được gõ là { khi nằm bên phải và } khi nằm bên trái — chỉ là chuyện quay hình cho đúng chiều, ý nghĩa y hệt.
Bốn tổ hợp cần thuộc lòng¶
Ghép hai đầu lại thì ra một quan hệ hoàn chỉnh. Bốn tổ hợp hay gặp nhất:
| Cú pháp Mermaid | Tỉ lệ bản số | Đọc thành lời | Có trong truong_hoc không |
|---|---|---|---|
a ||--o{ b |
1:N | Mỗi b có đúng một a; mỗi a có 0..n b |
Có — lop ||--o{ hoc_sinh |
a |o--o| b |
1:1, hai phía đều tuỳ chọn | Mỗi b có 0..1 a; mỗi a có 0..1 b |
Có — giao_vien |o--o| lop |
a ||--|| b |
1:1, hai phía đều bắt buộc | Mỗi b có đúng một a, và ngược lại |
Không — xem hộp dưới |
a }o--o{ b |
M:N | Mỗi b có 0..n a; mỗi a có 0..n b |
Có ở mức ý niệm — hoc_sinh }o--o{ mon_hoc |
Vì sao truong_hoc không có ||--|| nào
||--|| nghĩa là "không bao giờ được phép tồn tại một bên mà thiếu bên kia". Áp vào cặp GIÁO VIÊN — LỚP thì thành: mọi lớp phải có chủ nhiệm, và mọi giáo viên phải chủ nhiệm một lớp. Trường có 8 giáo viên nhưng chỉ 6 lớp, nên điều đó bất khả thi.
Thực tế, ||--|| rất hiếm gặp và khó hiện thực: nếu cả hai phía đều bắt buộc thì bạn không chèn nổi dòng đầu tiên vào bảng nào cả — chèn lop trước thì thiếu giao_vien, chèn giao_vien trước thì thiếu lop. Muốn làm thật phải dùng ràng buộc hoãn (deferrable constraint) mà Bài 15 sẽ nhắc tới.
Đối chiếu Chen ↔ Crow's Foot¶
Bảng này để bạn dịch qua lại giữa hai bộ ký hiệu:
| Ý nghĩa | Chen | Crow's Foot |
|---|---|---|
| Tập thực thể mạnh | Hình chữ nhật, thuộc tính treo elip ra ngoài | Hình chữ nhật có danh sách cột bên trong |
| Tập thực thể yếu | Hình chữ nhật đôi | Không có ký hiệu riêng — vẽ như bảng thường |
| Mối quan hệ | Hình thoi giữa hai thực thể | Một đường nối, tên ghi trên đường |
| Quan hệ nhận diện | Hình thoi đôi | Không có ký hiệu riêng |
| Bản số 1 | Chữ 1 trên đường |
Nửa trong là vạch | |
| Bản số N / M | Chữ N hoặc M |
Nửa trong là chân quạ } |
| Tham gia toàn phần | Đường đôi | Nửa ngoài là vạch | |
| Tham gia bộ phận | Đường đơn | Nửa ngoài là vòng tròn o |
| Thuộc tính khoá | Elip gạch chân | Ghi PK sau tên cột |
| Thuộc tính đa trị | Elip đôi | Không vẽ được |
| Thuộc tính dẫn xuất | Elip nét đứt | Không vẽ được |
| Quan hệ bậc ba | Một hình thoi nối ba hình chữ nhật | Không vẽ được — phải tách thành bảng trung gian |
Ba thứ Crow's Foot làm mất¶
Nhìn ba dòng "Không vẽ được" ở cuối bảng trên. Đó chính là cái giá của sự gọn gàng:
- Thuộc tính đa trị và dẫn xuất biến mất. Crow's Foot chỉ liệt kê được những cột sẽ có thật trong bảng. Mà
tuoi(dẫn xuất) thì không lưu, còn số điện thoại đa trị thì đã bị tách sang bảng riêng. - Quan hệ bậc ba biến mất.
PHÂN CÔNG DẠYnối ba thực thể; Crow's Foot chỉ vẽ được đường nối hai đầu. Nên nó buộc phải xuất hiện dưới dạng một hình chữ nhậtphan_cong_daynối ba đường riêng — tức là đã bị chuyển thành bảng trước khi vẽ. - Thực thể yếu trông y hệt thực thể mạnh.
phu_huynhvàdiem_danhtrong sơ đồ dưới đây không có dấu hiệu nào cho biết chúng là thực thể yếu; thông tin đó chỉ còn sót lại ởON DELETE CASCADEtrong lược đồ.
Rút ra: hai bộ ký hiệu phục vụ hai mục đích khác nhau
| Chen | Crow's Foot | |
|---|---|---|
| Mức mô tả | Ý niệm — thế giới thực | Logic — gần với bảng |
| Dùng để | Nói chuyện với người dùng, ra đề thi | Thiết kế lược đồ, sinh CREATE TABLE |
| Vẽ trước hay sau | Trước | Sau, khi đã biết sẽ có những bảng nào |
Vì thế thứ tự học của khoá này là: Chen (Bài 10) → Crow's Foot (bài này) → thuật toán chuyển đổi nối hai thứ đó lại (Bài 14).
Cú pháp erDiagram của Mermaid¶
Một sơ đồ Crow's Foot trong Mermaid gồm hai phần, viết trong cùng một khối:
Phần 1 — các đường nối. Mỗi dòng một quan hệ:
Nhãn bắt buộc phải có và nên đặt trong ngoặc kép — nhãn tiếng Việt có dấu mà quên ngoặc kép là lỗi cú pháp.
Phần 2 — cột của từng bảng. Mỗi bảng một khối { }, mỗi dòng một cột theo thứ tự kiểu tên khoá:
Ba nhãn khoá mà Mermaid hiểu:
| Nhãn | Nghĩa | Tiếng Anh |
|---|---|---|
PK |
Khoá chính | primary key |
FK |
Khoá ngoại | foreign key |
UK |
Khoá duy nhất | unique key |
Một cột mang hai vai thì viết PK, FK — có dấu phẩy
Trong phan_cong_day, cột ma_gv vừa nằm trong khoá chính vừa là khoá ngoại. Viết đúng là:
Viết PK_FK hoặc PK FK (thiếu dấu phẩy) đều làm sơ đồ không render được. Đây là lỗi cú pháp hay gặp nhất khi gõ erDiagram bằng tay.
Bảng thuật ngữ¶
| Tiếng Việt | English | Nghĩa dễ hiểu |
|---|---|---|
| Ký hiệu Crow's Foot | Crow's Foot notation | Bộ ký hiệu vẽ ER gọn, bảng có cột bên trong, bản số ghi bằng ký hiệu đầu đường |
| Chân quạ | crow's foot | Ba nhánh toẽ ra sát hình chữ nhật — nửa tối đa, nghĩa là "nhiều" |
| Ký hiệu IE | Information Engineering notation | Tên gọi học thuật khác của Crow's Foot |
| Mức logic | logical level | Mức đã biết sẽ có bảng nào, cột nào — nhưng chưa chọn hệ quản trị |
🖼️ Sơ đồ¶
Sơ đồ 1 — Bốn ký hiệu, đọc thành lời¶
Sơ đồ nhỏ này chỉ để bạn nhìn thấy hình dạng thật của bốn ký hiệu trong bảng tra:
erDiagram
lop ||--o{ hoc_sinh : "1-N · mỗi HS đúng 1 lớp · mỗi lớp 0..n HS"
giao_vien |o--o| lop : "1-1 · mỗi lớp 0..1 GVCN · mỗi GV 0..1 lớp"
hoc_sinh }o--o{ mon_hoc : "M-N ý niệm · sẽ tách thành bảng diem"
lop ||--|{ hoc_sinh : "nếu bắt buộc mỗi lớp có ít nhất 1 HS"
Đọc từng đường, đi từ ký hiệu về phía thực thể mà nó chạm vào:
| Đường | Ký hiệu sát lop |
Ký hiệu sát đầu kia | Đọc thành lời |
|---|---|---|---|
| 1 | || đúng một |
o{ không hoặc nhiều |
Mỗi học sinh có đúng một lớp; mỗi lớp có 0 hoặc nhiều học sinh |
| 2 | o| không hoặc một |
|o không hoặc một |
Mỗi lớp có 0 hoặc 1 GVCN; mỗi giáo viên chủ nhiệm 0 hoặc 1 lớp |
| 3 | }o / o{ |
hai đầu đều "nhiều" | Mỗi học sinh nhiều môn; mỗi môn nhiều học sinh |
| 4 | || đúng một |
|{ một hoặc nhiều |
Giống đường 1, nhưng thêm điều kiện mỗi lớp phải có ít nhất một học sinh |
Đường thứ tư chỉ có ở đây để bạn thấy ký hiệu |{. Lược đồ thật không ép điều kiện đó — Bài 9 đã giải thích: ràng buộc ở phía "nhiều" thì SQL không cưỡng chế được bằng NOT NULL.
Sơ đồ 2 — Biểu đồ ER hoàn chỉnh của truong_hoc¶
Đây là sơ đồ tham chiếu của cả khoá học. Bài 14 sẽ chứng minh nó sinh ra từ sơ đồ Chen của Bài 10, và Cấp 2 sẽ còn quay lại nó nhiều lần.
erDiagram
giao_vien |o--o| lop : "chủ nhiệm"
giao_vien ||--o{ phan_cong_day : "được phân công"
mon_hoc ||--o{ phan_cong_day : "của môn"
lop ||--o{ phan_cong_day : "dạy tại lớp"
lop ||--o{ hoc_sinh : "gồm có"
hoc_sinh ||--o{ phu_huynh : "có người thân"
hoc_sinh ||--o{ diem : "đạt"
mon_hoc ||--o{ diem : "của môn"
hoc_sinh ||--o{ muon_sach : "mượn"
sach ||--o{ muon_sach : "được mượn"
hoc_sinh ||--o{ diem_danh : "được điểm danh"
giao_vien {
char ma_gv PK
varchar ho_ten
date ngay_sinh
varchar gioi_tinh
varchar mon_chuyen_mon
varchar email UK
numeric luong
}
lop {
char ma_lop PK
varchar ten_lop UK
smallint khoi
varchar nam_hoc
char ma_gvcn FK, UK
}
hoc_sinh {
char ma_hs PK
varchar ho_ten
date ngay_sinh
varchar gioi_tinh
varchar dia_chi
char ma_lop FK
}
phu_huynh {
char ma_ph PK
varchar ho_ten
varchar so_dien_thoai
varchar quan_he
char ma_hs FK
}
mon_hoc {
char ma_mon PK
varchar ten_mon UK
smallint so_tiet_tuan
}
phan_cong_day {
char ma_gv PK, FK
char ma_mon PK, FK
char ma_lop PK, FK
smallint hoc_ky PK
}
diem {
serial ma_diem PK
char ma_hs FK
char ma_mon FK
smallint hoc_ky
varchar loai_diem
numeric diem_so
date ngay_nhap
}
sach {
char ma_sach PK
varchar ten_sach
varchar tac_gia
smallint nam_xuat_ban
smallint so_luong
}
muon_sach {
serial ma_muon PK
char ma_hs FK
char ma_sach FK
date ngay_muon
date ngay_tra_du_kien
date ngay_tra_thuc_te
}
diem_danh {
serial ma_dd PK
char ma_hs FK
date ngay
varchar trang_thai
varchar ly_do
}
Năm điều đáng chú ý khi đọc sơ đồ này:
- Chỉ có duy nhất một đường
|o--o|— đườnggiao_vien↔lop. Đó là quan hệ 1:1 duy nhất của database, và nó được hiện thực bằngma_gvcn FK, UK. Chính chữUKđó là cái làm nên 1:1. - Mười đường còn lại đều là
||--o{— tức 1:N. Đây là loại quan hệ áp đảo trong mọi database thật. phan_cong_daycó ba đường chạm vào nó. Ở sơ đồ Chen (Bài 10) nó là một hình thoi bậc ba; ở đây nó đã hoá thành một hình chữ nhật. Crow's Foot không vẽ nổi hình thoi ba nhánh, nên buộc phải làm vậy.diemcũng là một hình chữ nhật, dù ở mức ý niệm nó là quan hệ M:N. Đườnghoc_sinh }o--o{ mon_hoctrong Sơ đồ 1 chính là nó trước khi bị tách. So hai sơ đồ với nhau là thấy ngay M:N được hiện thực thế nào.phu_huynhvàdiem_danhtrông y hệt các bảng khác. Chúng là thực thể yếu (Bài 9), nhưng Crow's Foot không có ký hiệu cho điều đó. Muốn biết, phải mở lược đồ ra xemON DELETE CASCADE.
Vì sao diem không nối thẳng hoc_sinh với mon_hoc bằng }o--o{?
Vẽ như vậy cũng đúng, nhưng ở một mức khác. }o--o{ là cách vẽ ở mức ý niệm: nó nói "có quan hệ nhiều–nhiều" mà chưa nói hiện thực ra sao.
Sơ đồ 2 vẽ ở mức logic — mức đã quyết định sẽ có những bảng nào. Ở mức đó, M:N luôn đã được tách thành bảng trung gian, nên không còn đường }o--o{ nào cả.
Mẹo kiểm tra nhanh: một sơ đồ Crow's Foot có kèm danh sách cột mà vẫn còn đường }o--o{ thì sơ đồ đó chưa xong việc — còn ít nhất một bảng chưa được vẽ ra.
💻 Thực hành¶
Suy ra ký hiệu Crow's Foot từ chính lược đồ¶
Điều hay nhất của Crow's Foot: mọi ký hiệu đều đọc được ra từ database, không cần đoán. Quy tắc gọn trong hai dòng:
| Muốn biết | Nhìn vào | Kết quả |
|---|---|---|
| Nửa tối thiểu ở phía cha | cột khoá ngoại có NOT NULL không |
có → | · không → o |
| Nửa tối đa ở phía con | cột khoá ngoại có UNIQUE không |
có → | (1:1) · không → { (1:N) |
Câu lệnh sau tự sinh ra bảng đó cho mọi khoá ngoại trong database:
SELECT c.conrelid::regclass AS bang_con,
a.attname AS cot_khoa_ngoai,
c.confrelid::regclass AS bang_cha,
a.attnotnull AS bat_buoc,
EXISTS (SELECT 1
FROM pg_constraint u
WHERE u.conrelid = c.conrelid
AND u.contype IN ('p', 'u')
AND u.conkey = ARRAY[a.attnum]) AS duy_nhat
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.connamespace = 'public'::regnamespace
ORDER BY c.conrelid::regclass::text, a.attname;
Kết quả 11 dòng — đúng bằng số cột khoá ngoại của 10 bảng. Đọc chúng thành ký hiệu:
| Dòng trong kết quả | bat_buoc |
duy_nhat |
Ký hiệu Crow's Foot |
|---|---|---|---|
diem / ma_hs, diem / ma_mon |
t |
f |
||--o{ về phía diem |
diem_danh / ma_hs |
t |
f |
hoc_sinh ||--o{ diem_danh |
hoc_sinh / ma_lop |
t |
f |
lop ||--o{ hoc_sinh |
lop / ma_gvcn |
f |
t |
giao_vien |o--o| lop |
muon_sach / ma_hs, ma_sach |
t |
f |
||--o{ về phía muon_sach |
phan_cong_day / ba cột |
t |
f |
||--o{ về phía phan_cong_day |
phu_huynh / ma_hs |
t |
f |
hoc_sinh ||--o{ phu_huynh |
Đúng một dòng có duy_nhat = t, và đó chính là đường |o--o| duy nhất của Sơ đồ 2.
Xem tận mắt ràng buộc làm nên quan hệ 1:1¶
SELECT conname, contype, pg_get_constraintdef(oid) AS dinh_nghia
FROM pg_constraint
WHERE conrelid = 'lop'::regclass
AND contype IN ('p', 'u', 'f')
ORDER BY contype, conname;
Trong kết quả có ba dòng đáng chú ý:
lop_pkey—PRIMARY KEY (ma_lop)lop_ma_gvcn_key—UNIQUE (ma_gvcn)← đây là chữUKtrong sơ đồlop_ma_gvcn_fkey—FOREIGN KEY (ma_gvcn) REFERENCES giao_vien(ma_gv) ON DELETE SET NULL← đây là chữFK
Hai ràng buộc riêng biệt, đặt trên cùng một cột, và phải có cả hai thì mới ra |o--o|. Bỏ lop_ma_gvcn_key đi thì sơ đồ tụt xuống thành |o--o{ — mỗi giáo viên chủ nhiệm được nhiều lớp.
Kiểm chứng bằng dữ liệu: UNIQUE đang làm gì¶
SELECT count(*) AS so_lop,
count(ma_gvcn) AS so_lop_co_gvcn,
count(DISTINCT ma_gvcn) AS so_gvcn_khac_nhau
FROM lop;
Kết quả: 6, 5, 5.
Đọc ra ký hiệu:
6lớp nhưng chỉ5lớp có chủ nhiệm → phíalopđược phép trống → nửa ngoài là vòng tròn:o|.5lớp có chủ nhiệm ứng với đúng5giáo viên khác nhau → không ai ôm hai lớp → nửa trong là vạch:|.
Ghép lại: |o--o|. Sơ đồ và dữ liệu khớp nhau.
So sánh với một quan hệ 1:N¶
Kết quả: 40 và 6.
Bốn mươi dòng dồn vào sáu giá trị — nghĩa là có lớp chứa nhiều học sinh. Đó là chân quạ. Và vì ma_lop là NOT NULL nên phía lop là vạch. Ghép lại: lop ||--o{ hoc_sinh.
Bảng nào là bảng trung gian¶
Bảng trung gian có một dấu hiệu rất dễ nhận: có từ hai khoá ngoại trở lên.
SELECT conrelid::regclass AS bang,
count(*) AS so_khoa_ngoai
FROM pg_constraint
WHERE contype = 'f'
AND connamespace = 'public'::regnamespace
GROUP BY 1
HAVING count(*) >= 2
ORDER BY 2 DESC, conrelid::regclass::text;
Ba bảng hiện ra: phan_cong_day (3 khoá ngoại), diem (2) và muon_sach (2).
Đúng ba hình chữ nhật mà Bài 10 đã nói là vốn là hình thoi trong ký hiệu Chen. Crow's Foot buộc chúng phải hiện ra thành bảng — và câu SQL trên tìm lại được chúng chỉ bằng cách đếm khoá ngoại.
⚠️ Lỗi thường gặp¶
Lỗi 1: Đọc ký hiệu ngược đầu
Nhìn lop ||--o{ hoc_sinh rồi đọc thành "một lớp có đúng một học sinh". Sai hoàn toàn.
Ký hiệu || nằm sát lop, nên nó nói về lop: mỗi học sinh có đúng một lớp. Ký hiệu chân quạ nằm sát hoc_sinh, nên nó nói về hoc_sinh: mỗi lớp có nhiều học sinh.
Cách đọc chống sai: đặt ngón tay lên một ký hiệu, rồi hỏi "ứng với một thực thể ở đầu kia, có bao nhiêu thực thể ở đầu này?"
Lỗi 2: Quên nửa 'tối thiểu'
Vẽ lop }|--o{ hoc_sinh rồi bảo "xong, 1:N đấy". Nhưng }| nghĩa là "một hoặc nhiều lớp cho mỗi học sinh" — tức là một học sinh học nhiều lớp cùng lúc. Chân quạ } sát lop đã nói "tối đa nhiều" rồi.
Mỗi ký hiệu có hai ký tự và cả hai đều mang nghĩa. Bỏ qua một nửa là mô tả sai lược đồ.
Lỗi 3: Vẽ }o--o{ rồi vẫn liệt kê cột
Một sơ đồ Crow's Foot có khai báo cột là sơ đồ ở mức logic, tức là mức đã biết database sẽ có những bảng nào. Ở mức đó, quan hệ M:N phải đã được tách thành bảng trung gian rồi.
Còn đường }o--o{ nghĩa là "tôi chưa quyết định hiện thực ra sao". Hai thứ đó không đi cùng nhau được. Cứ để }o--o{ mà vẫn liệt kê cột thì người đọc sẽ hỏi: "vậy diem_so lưu ở bảng nào?"
Lỗi 4: Viết PK_FK trong erDiagram
PK_FK, và cả sơ đồ sẽ không hiện ra — chỉ còn một khung báo lỗi đỏ.
Viết đúng là PK, FK — hai nhãn, ngăn bằng dấu phẩy.
Lỗi 5: Tưởng Crow's Foot thay thế được Chen
Vì Crow's Foot gọn hơn và các công cụ đều dùng nó, rất dễ kết luận "học Chen làm gì cho mệt".
Nhưng thử vẽ tình huống này bằng Crow's Foot: "một học sinh có nhiều số điện thoại". Bạn sẽ buộc phải tạo ngay một bảng hoc_sinh_sdt — tức là đã quyết định xong cách hiện thực trước cả khi kịp hỏi cô hiệu trưởng xem có cần lưu gì thêm về số điện thoại đó không.
Chen cho phép bạn ghi "đây là thuộc tính đa trị" rồi hoãn quyết định lại. Đó là giá trị của nó, và là lý do Bài 14 tồn tại.
✍️ Bài tập¶
-
Đọc thành lời (theo mẫu "mỗi ... có ... ; mỗi ... có ...") từng đường dưới đây:
a.
sach ||--o{ muon_sachb.giao_vien |o--o| lopc.phong_hoc ||--|{ thiet_bid.mon_hoc }o--o{ giao_vien -
Trường mở thêm câu lạc bộ. Luật: mỗi học sinh tham gia tối đa một câu lạc bộ, có thể không tham gia câu lạc bộ nào; mỗi câu lạc bộ phải có ít nhất một thành viên. Viết dòng
erDiagramđúng cho quan hệ này, và nói xem lược đồ SQL cần những ràng buộc gì. -
Nhìn Sơ đồ 2 và trả lời: nếu xoá ràng buộc
UNIQUEtrênlop.ma_gvcn, ký hiệu của đườnggiao_vien↔lopđổi thành gì? Câu chuyện đời thực tương ứng là gì? -
Viết câu SQL đếm xem mỗi bảng trong
truong_hoccó bao nhiêu đường nối chạm vào nó trong Sơ đồ 2 — tức tổng số khoá ngoại đi ra cộng số khoá ngoại trỏ tới. Bảng nào nhiều nhất? -
Một bạn vẽ sơ đồ sau cho hệ thống thư viện và nói "em vẽ theo Crow's Foot":
Chỉ ra hai lỗi và viết lại cho đúng.
Đáp án
Câu 1.
| Đọc thành lời | |
|---|---|
| a | Mỗi lượt mượn ứng với đúng một cuốn sách; mỗi cuốn sách có 0 hoặc nhiều lượt mượn |
| b | Mỗi lớp có 0 hoặc 1 giáo viên chủ nhiệm; mỗi giáo viên chủ nhiệm 0 hoặc 1 lớp |
| c | Mỗi thiết bị thuộc đúng một phòng học; mỗi phòng học có ít nhất một thiết bị |
| d | Mỗi giáo viên dạy 0 hoặc nhiều môn; mỗi môn do 0 hoặc nhiều giáo viên dạy |
Lưu ý câu c: ký hiệu |{ (một hoặc nhiều) là chỗ duy nhất trong bốn câu có ràng buộc tham gia toàn phần ở phía nhiều — và đó cũng là ràng buộc mà SQL không cưỡng chế nổi bằng NOT NULL (Bài 9).
Câu 2.
Sai! Đọc lại: || sát cau_lac_bo nghĩa là mỗi học sinh phải có đúng một câu lạc bộ — trái với đề bài (được phép không tham gia). Viết đúng là:
|osátcau_lac_bo: mỗi học sinh có 0 hoặc 1 câu lạc bộ.|{sáthoc_sinh: mỗi câu lạc bộ có ít nhất một học sinh.
Về lược đồ SQL: thêm cột hoc_sinh.ma_clb là khoá ngoại cho phép NULL (vì |o) và không có UNIQUE (vì nhiều thành viên chung một câu lạc bộ). Còn điều kiện "mỗi câu lạc bộ ít nhất một thành viên" thì không ép được bằng ràng buộc cột — cần trigger hoặc kiểm tra ở tầng ứng dụng.
Câu 3.
Đổi từ giao_vien |o--o| lop thành giao_vien |o--o{ lop.
Câu chuyện đời thực: một giáo viên được phép chủ nhiệm nhiều lớp cùng lúc. Quan hệ tụt từ 1:1 xuống 1:N. Nửa ngoài vẫn là vòng tròn (lớp vẫn được phép chưa có chủ nhiệm), chỉ nửa trong đổi từ vạch sang chân quạ.
Đây đúng là điều Bài 8 đã cảnh báo: UNIQUE là toàn bộ khác biệt giữa 1:1 và 1:N ở mức bảng.
Câu 4.
SELECT bang, sum(so_duong) AS so_duong_cham_vao
FROM (
SELECT conrelid::regclass::text AS bang, count(*) AS so_duong
FROM pg_constraint
WHERE contype = 'f' AND connamespace = 'public'::regnamespace
GROUP BY 1
UNION ALL
SELECT confrelid::regclass::text, count(*)
FROM pg_constraint
WHERE contype = 'f' AND connamespace = 'public'::regnamespace
GROUP BY 1
) t
GROUP BY bang
ORDER BY 2 DESC, 1;
hoc_sinh đứng đầu: nó có 1 khoá ngoại đi ra (ma_lop) và 4 khoá ngoại trỏ tới (phu_huynh, diem, muon_sach, diem_danh) — tổng 5 đường.
Điều này khớp đúng với Sơ đồ 2: hoc_sinh là trung tâm của cả biểu đồ. Trong một database thật, bảng có nhiều đường chạm vào nhất thường là bảng quan trọng nhất — và cũng là bảng nguy hiểm nhất khi xoá dòng, điều Bài 15 sẽ bàn.
Câu 5. Hai lỗi:
- Trộn hai mức. Đường
}o--o{là mức ý niệm (chưa tách M:N), nhưng khối khai báo cột lại là mức logic. Đã vẽmuon_sachra thì phải bỏ đường}o--o{đi. muon_sachkhông nối vào đâu cả, và thiếu hai khoá ngoại. Một bảng trung gian mà không cóma_hs,ma_sachthì không trung gian được gì.
Viết lại:
🔑 Tóm tắt¶
- Ký hiệu Crow's Foot vẽ mỗi bảng thành một hình chữ nhật có danh sách cột bên trong, và gói bản số cùng ràng buộc tham gia vào một ký hiệu hai nửa ở mỗi đầu đường.
- Nửa ngoài trả lời tối thiểu bao nhiêu (
o= 0,|= 1); nửa trong trả lời tối đa bao nhiêu (|= 1,}= nhiều). Ký hiệu ở đầu nào thì mô tả thực thể ở đầu đó. - Bốn tổ hợp cần thuộc:
||--o{là 1:N,|o--o|là 1:1 tuỳ chọn,||--||là 1:1 bắt buộc (rất hiếm),}o--o{là M:N chưa tách. - Crow's Foot mất ba thứ so với Chen: thuộc tính đa trị, thuộc tính dẫn xuất, và quan hệ bậc từ ba trở lên — nên nó là ký hiệu ở mức logic, vẽ sau khi đã biết sẽ có bảng nào.
- Mọi ký hiệu trong Sơ đồ 2 đều đọc ngược lại được từ lược đồ:
NOT NULLquyết định nửa ngoài,UNIQUEquyết định nửa trong — vàtruong_hocchỉ có đúng một đường|o--o|, ở cộtlop.ma_gvcn.
⬅️ Bài 10 — Biểu đồ ER ký hiệu Chen · ➡️ Bài 12 — Bảy loại khoá trong cơ sở dữ liệu