Database Design & ER Model
entity หรือ attribute · relationship และ cardinality · total vs partial participation · weak entity · สัญลักษณ์แบบ Silberschatz
วิดีโอที่อาจารย์ให้ดูก่อนเรียนบทนี้ (แบ่ง 2 ตอน):
11.0ER Diagram คืออะไร ใช้ทำอะไร
ก่อนจะลงมือเขียน CREATE TABLE เราต้องตอบให้ได้ก่อนว่าระบบนี้เก็บอะไรบ้าง และของแต่ละอย่างเกี่ยวข้องกันยังไง
ER diagram (Entity-Relationship diagram) คือภาพที่ใช้ตอบคำถามนั้น — เป็นการจำลองโลกจริงออกมาเป็นรูป
ก่อนแปลงเป็นตารางในบทที่ 12
| ใน ER diagram | คืออะไร | พอแปลงเป็นฐานข้อมูลจะกลายเป็น |
|---|---|---|
| Entity (entity set) |
“สิ่งของ” ที่เราต้องเก็บข้อมูลของมัน และมีอยู่หลายตัว เช่น นักศึกษา รายวิชา พนักงาน | ตาราง 1 ตาราง · แต่ละ entity 1 ตัว = 1 แถว |
| Attribute | ข้อมูลย่อยที่บรรยาย entity นั้น เช่น ชื่อ วันเกิด เงินเดือน | คอลัมน์ |
| Relationship (relationship set) |
ความเกี่ยวข้องระหว่าง entity เช่น “นักศึกษาลงทะเบียนรายวิชา” | foreign key หรือ ตารางใหม่ แล้วแต่ชนิดความสัมพันธ์ (บทที่ 12) |
กล่องสี่เหลี่ยมใน diagram หมายถึง entity set (ทั้งกลุ่ม) ไม่ใช่คนคนเดียว — เหมือนกล่องหมายถึง “ตาราง” ไม่ใช่ “แถว”
relationship set ก็เช่นกัน: ข้าวหลามตัดหมายถึงความสัมพันธ์ทั้งชนิด ไม่ใช่การจับคู่ครั้งเดียว
สิ่งที่ ER diagram บอกเพิ่มจากแค่รายชื่อตาราง คือ 3 คำถามที่จะเรียนต่อไปในบทนี้ และทั้งสามข้อมีผลโดยตรงต่อ schema ที่ได้:
- Cardinality (หัวข้อ 11.3) — จับคู่กันได้กี่ตัว → ตัดสินว่า FK ไปอยู่ตารางไหน หรือต้องสร้างตารางใหม่
- Participation (หัวข้อ 11.4) — ต้องจับคู่ไหม หรือปล่อยว่างได้ → ตัดสินว่า FK เป็น
NOT NULLหรือไม่ - Weak entity (หัวข้อ 11.5) — มีของที่อยู่เองไม่ได้ไหม → ตัดสินว่า PK ต้องประกอบจากอะไร
11.1สัญลักษณ์ ER แบบ Silberschatz
อาจารย์ใช้สัญลักษณ์แบบ Silberschatz (ตามตำรา Database System Concepts) ซึ่งเป็นแบบเดียวกับที่มีใน legend ของแบบฝึกหัด ต้องวาดเป็นและอ่านออกทั้งสองทาง
เพราะ weak entity อยู่เองไม่ได้ ความสัมพันธ์นี้จึงเป็น total participation เสมอ (เส้นคู่) และวาดข้าวหลามตัดสองชั้นเพื่อบอกว่าไม่ใช่ความสัมพันธ์ธรรมดา · รายละเอียดอยู่หัวข้อ 11.5
11.2Entity หรือ Attribute — ตัดสินยังไง
นี่คือคำถามข้อแรกของแบบฝึกหัดในคาบ (6 คะแนน) โจทย์ให้สถานการณ์ "GlobalMart" แพลตฟอร์ม e-commerce: ลูกค้ามี customer ID ไม่ซ้ำ · ลูกค้าหนึ่งคนมี shipping address ได้หลายที่ · แต่ละ order มีเลขที่ไม่ซ้ำ · สินค้าจัดเป็นหมวดหมู่แบบลำดับชั้น (Electronics > Smartphones > Android Phones) · ลูกค้าเขียน review (rating + ข้อความ) ให้สินค้าที่ซื้อ
เป็น ATTRIBUTE เมื่อ — เป็นค่าเดี่ยว ๆ ที่แบ่งย่อยไม่ได้ และเกิดได้ครั้งเดียวต่อพ่อแม่หนึ่งตัว
| รายการ | E / A | เหตุผล |
|---|---|---|
| a) Customer | E | มีข้อมูลย่อยหลายอย่าง (ชื่อ อีเมล ที่อยู่) และมี customer ID ไม่ซ้ำ เป็นของตัวเอง |
| b) Shipping address | E | มีข้อมูลย่อย (ถนน เมือง รหัสไปรษณีย์) และลูกค้าหนึ่งคนมีได้หลายที่ → multi-valued จึงต้องแยกเป็น entity |
| c) Loyalty-points balance | A | เป็นจำนวนเต็มค่าเดียว ไม่มีข้อมูลย่อยลงไปอีก และมีค่าเดียวต่อลูกค้าหนึ่งคน |
| d) Product Category | E | เป็นลำดับชั้น (Electronics > Smartphones > Android) หมวดหมู่จึงต้องอ้างถึงหมวดหมู่แม่ของตัวเอง = มีความสัมพันธ์ของตัวเอง |
| e) Star rating ของ review | A | เป็นตัวเลข 1–5 ค่าเดียว ไม่มีค่าย่อย |
| f) Review | E | มีทั้ง rating และข้อความ (หลาย attribute) และเชื่อมทั้งลูกค้าและสินค้า |
11.3Cardinality — 1:1, 1:N, M:N
Cardinality บอกว่า entity หนึ่งตัวจับคู่กับอีกฝั่งได้กี่ตัว · ตัวเลข 1 แปลว่าได้มากที่สุดหนึ่งตัว ส่วน N (หรือ M) แปลว่าได้หลายตัว
ถาม: “นักศึกษา 1 คน อยู่ได้กี่แผนก?” → 1 แผนก · ถามกลับ: “แผนก 1 แผนก มีนักศึกษาได้กี่คน?” → หลายคน
→ ได้ 1 : N โดยฝั่ง Department = 1 และฝั่ง Student = N
ตัวช่วยกันสับสน: เลขที่เขียนกำกับข้างฝั่งไหน คือ “ฝั่งนั้นมีได้กี่ตัวต่อคู่หนึ่งครั้ง” — ฝั่งที่เป็น N คือฝั่งที่มีหลายแถวชี้ไปหาแถวเดียวอีกฝั่ง และนั่นคือฝั่งที่ FK จะไปอยู่
| แบบ | อ่านว่า | ตัวอย่าง |
|---|---|---|
| 1 : 1 | แต่ละฝั่งจับคู่ได้มากที่สุดฝั่งละหนึ่ง | นักศึกษา 1 คน มีโปรไฟล์ได้ 1 อัน · โปรไฟล์ 1 อัน เป็นของนักศึกษา 1 คน |
| 1 : N | ฝั่งซ้ายหนึ่งตัว จับคู่ได้หลายตัวฝั่งขวา · แต่ฝั่งขวาหนึ่งตัว จับคู่ได้ตัวเดียวฝั่งซ้าย | แผนก 1 แผนกมีนักศึกษาหลายคน · นักศึกษา 1 คนอยู่แผนกเดียว |
| M : N | หลายต่อหลายทั้งสองทาง | นักศึกษา 1 คนลงได้หลายวิชา · วิชา 1 วิชามีนักศึกษาหลายคน |
กดเลือกแต่ละแบบด้านล่าง เพื่อดูว่ามันกลายเป็น schema หน้าตาแบบไหน (กฎเต็มอยู่บทที่ 12):
11.4Participation Constraint — Total vs Partial
Participation ตอบคำถามเดียว: “แถวทุกแถวของ entity ฝั่งนี้ ต้องมีคู่ในความสัมพันธ์นี้หรือเปล่า — หรือปล่อยให้บางแถวไม่มีคู่เลยก็ได้?”
| แบบ | วาดยังไง | แปลว่า | ตัวอย่างประโยคในโจทย์ |
|---|---|---|---|
| Total | เส้นคู่ ═ | ทุกแถวของ entity ฝั่งนั้นต้องมีคู่ · ห้ามมีแถวที่ลอยอยู่เฉย ๆ | “พนักงานทุกคนต้องสังกัดแผนก” · “สัตว์ทุกตัวต้องมีเจ้าของ” |
| Partial | เส้นเดี่ยว — | บางแถวจะไม่มีคู่ก็ได้ · ไม่บังคับ | “บางแผนกอาจไม่มีพนักงานเลย” · “ไม่ใช่พนักงานทุกคนที่ได้รับมอบหมายโปรเจกต์” |
Cardinality ถามว่า “ได้กี่ตัว” → 1 หรือ N · วาดด้วยตัวเลข
Participation ถามว่า “ต้องมีไหม” → total หรือ partial · วาดด้วยจำนวนเส้น
ลองอ่านสองประโยคนี้ ทั้งคู่เป็น 1 : N เหมือนกันเป๊ะ แต่ participation ต่างกัน:
• “นักศึกษาทุกคนต้องมีอาจารย์ที่ปรึกษา” → 1:N + total ฝั่งนักศึกษา
• “นักศึกษาอาจยังไม่มีอาจารย์ที่ปรึกษา” → 1:N + partial ฝั่งนักศึกษา
และ participation เป็นของ “แต่ละฝั่ง” แยกกัน — ฝั่งซ้ายเป็น total ฝั่งขวาเป็น partial พร้อมกันได้ (ซึ่งเป็นเคสที่เจอบ่อยที่สุด)
อ่านจากโจทย์ยังไงว่าเป็น total หรือ partial
ให้จับคำบอกใบ้ในตัวโจทย์ แล้วดูว่าคำนั้นพูดถึง entity ฝั่งไหน — participation ไปแปะฝั่งนั้น
ทุก… · …ทุกคนต้อง… · จำเป็นต้อง · must · every · each … has to
อยู่เองไม่ได้ / ต้องมี … เสมอ
บาง… · อาจจะ · ไม่จำเป็นต้อง · may · some · optional · can be
ยังไม่มีก็ได้ / ชั่วคราวไม่มี
มันคุมข้อมูลจริงยังไง — ลองกดสลับดู
participation ไม่ใช่แค่สัญลักษณ์บนกระดาษ มันแปลไปเป็นกฎที่ฐานข้อมูลบังคับจริง ลองสลับดูว่าแถวแบบไหนถูกยอมรับและแถวแบบไหนถูกปฏิเสธ
NOT NULL (ปล่อยว่างไม่ได้ = ต้องมีคู่เสมอ)Partial → FK ปล่อยให้เป็น
NULL ได้ (ไม่ต้องเขียนอะไรเพิ่ม เพราะ SQL อนุญาต NULL อยู่แล้ว)ข้อจำกัดที่ควรรู้:
NOT NULL คุมได้เฉพาะฝั่งที่ถือ FK เท่านั้น
ถ้าอยากบังคับว่า “ทุกแผนกต้องมีพนักงานอย่างน้อยหนึ่งคน” SQL ธรรมดาทำไม่ได้ ต้องใช้ trigger หรือเช็กในแอป
— ในข้อสอบจึงมักเจอ total อยู่ฝั่งที่ถือ FK
ทีนี้ลองทำโจทย์จริง
โจทย์ข้อ 2 ในแบบฝึกหัด (5 คะแนน): พนักงาน (Employees) ทุกคนต้องสังกัดแผนก (Departments) เพียงแผนกเดียว
แต่บางแผนกอาจไม่มีพนักงานเลยชั่วคราว
a) วาด ER diagram แสดง Works_In พร้อมระบุ total / partial participation และ cardinality (3 คะแนน)
b) อธิบายความหมายของ participation constraint นี้ในเชิงสถานการณ์จริง (2 คะแนน)
ลองแกะโจทย์เองก่อน — คำใบ้ทีละคำ
“พนักงานทุกคนต้องสังกัดแผนก” → คำว่า “ทุกคนต้อง” = total · พูดถึง Employees → เส้นคู่ฝั่ง Employees
“บางแผนกอาจไม่มีพนักงานเลย” → “บาง…อาจ” = partial · พูดถึง Departments → เส้นเดี่ยวฝั่ง Departments
ความสัมพันธ์นี้เป็น 1:N — พนักงาน 1 คนได้ 1 แผนก · แผนก 1 แผนกมีพนักงานได้หลายคน
FK dept_id จึงอยู่ที่ตาราง Employees (ฝั่ง many) และเป็น NOT NULL เพราะฝั่ง Employees เป็น total
ระวังมาก — สไลด์ข้อนี้เขียนเลขคนละแบบกับตัวอย่างใน 11.3
ใน 11.3 ใช้แบบ look-here (เลขที่เขียนข้างฝั่งไหน = ฝั่งนั้นมีได้กี่ตัว) → Department เขียน 1, Student เขียน N
แต่สไลด์ข้อสอบข้อนี้ใช้แบบ look-across (เลขที่ปลายเส้นบอกว่า “อีกฝั่งหนึ่งมีได้กี่ตัว”) →
ฝั่ง Employees เขียน 1 · ฝั่ง Departments เขียน 0..N ตามที่เห็นในรูปข้างล่างและในเกณฑ์ให้คะแนน
วิธีกันเสียคะแนน: เขียนประโยคกำกับ diagram ไปด้วยเสมอ —
“พนักงาน 1 คน → 1 แผนก · แผนก 1 แผนก → พนักงานหลายคน · FK dept_id อยู่ที่ Employees”
ประโยคนี้ถูกไม่ว่าจะใช้ convention ไหน
เฉลย + เกณฑ์ให้คะแนน
Partial participation (1 คะแนน): บางแผนกอาจจะไม่มีพนักงาน — บางแถวใน entity Departments อาจจะไม่มีส่วนร่วม · บางแผนกอาจมีพนักงานคนเดียวหรือมากกว่าหนึ่งคน
เกณฑ์ให้คะแนนข้อ 2a (3 คะแนน): มี entity Employees, Departments และ relationship Works_In เชื่อมกัน (1) · ฝั่ง Employees = total participation เส้นคู่หรือเส้นทึบ (0.5) · ฝั่ง Departments = partial participation เส้นเดี่ยว (0.5) · cardinality ถูก: Employees = 1, Departments = N หรือ 0..N (1)
11.5Weak Entity
entity ปกติจะมี primary key ของตัวเอง ที่ระบุตัวมันได้เฉพาะตัวทั้งตาราง เช่น student_id, pet_id
แต่บางอย่างไม่มีคีย์แบบนั้น — มันแยกออกจากกันได้ก็ต่อเมื่อรู้ก่อนว่ามันเป็นของใคร · แบบนั้นเรียกว่า weak entity
2. ไม่มี PK ของตัวเอง — ค่าที่มีอยู่ซ้ำกันได้เมื่อดูทั้งตาราง จึงต้องยืม PK ของเจ้าของมาประกอบ
3. มี discriminator (partial key) — attribute ที่แยกความต่างได้ภายในเจ้าของเดียวกัน แต่ซ้ำได้ข้ามเจ้าของ · วาดด้วยเส้นใต้ประ
ทำไมถึงต้องมีเรื่องนี้ — ดูจากข้อมูลจริง
สมมติห้องสมุดเก็บ “บท” ของหนังสือ ลองดูตารางนี้แล้วถามตัวเองว่า chapter_no เพียงตัวเดียวเป็น PK ได้ไหม
| book_id PK | title |
|---|---|
| B01 | Database Systems |
| B02 | Operating Systems |
| book_id | chapter_no | title |
|---|---|---|
| B01 | 1 | Introduction |
| B01 | 2 | Relational Model |
| B02 | 1 | Processes |
| B02 | 2 | Threads |
แต่ภายในหนังสือเล่มเดียวกัน chapter_no ไม่ซ้ำแน่นอน → เลยต้องใช้ book_id คู่กับ chapter_no ถึงจะระบุได้เฉพาะตัว
นี่คือความหมายของ discriminator:
chapter_no แยกบทออกจากกันได้เฉพาะเมื่อรู้ว่าอยู่เล่มไหน
จึงเรียกว่า partial key — เป็นคีย์ได้แค่บางส่วน ต้องมีอีกส่วนมาเติม→ PRIMARY KEY (book_id, chapter_no)
ตัวอย่างอื่นที่เจอบ่อย: Chapter ของ Book · Vaccination dose ของ Pet · Dependent (ผู้ติดตาม) ของ Employee · OrderItem ของ Order · Room ของ Building — สังเกตว่าทุกตัว “เลขที่ N ของ X” ซ้ำข้าม X ได้ทั้งนั้น
ทีนี้ลองทำโจทย์จริง
โจทย์ข้อ 3 ในแบบฝึกหัด (5 คะแนน): ระบบห้องสมุดมี Book และ Chapter · แต่ละเล่มมีหลายบท ·
เลขบท (chapter number) ไม่ซ้ำภายในเล่มเดียวกัน แต่ซ้ำได้ข้ามเล่ม · และบทมีอยู่ไม่ได้ถ้าไม่มีหนังสือ
a) วาด ER diagram แสดง Book และ Chapter (ใช้สัญลักษณ์ระบุ weak entity) ·
b) Chapter เป็น weak entity หรือไม่ เพราะเหตุใด · c) cardinality ของ Book–Chapter = ?
ลองตอบเองก่อน แล้วค่อยเทียบกับ diagram และเฉลยข้างล่าง — สังเกตว่าโจทย์เขียนเงื่อนไขทั้ง 3 ข้อของ weak entity ไว้ให้ครบแล้ว
เฉลยข้อ 3 + เกณฑ์ให้คะแนน
b) Chapter เป็น weak entity หรือไม่ เพราะเหตุใด (2 คะแนน):
เป็น weak entity (1 คะแนน) เพราะ Chapter อยู่เองไม่ได้ ต้องมีหนังสือที่ Chapter นั้นเป็นองค์ประกอบ (1 คะแนน)
เหตุผลเสริมที่ใช้ได้: chapter_no ไม่ซ้ำภายในเล่มเดียวกัน แต่ซ้ำข้ามเล่มได้ จึงไม่มี PK ของตัวเอง ต้องพึ่ง book_id
c) cardinality ของ Book–Chapter (1 คะแนน): 1 : N
แปลงเป็น schema:
Chapter(book_id, chapter_no, title) โดย
PRIMARY KEY (book_id, chapter_no) และ FOREIGN KEY (book_id) REFERENCES Book(book_id)
11.6ฝึกอ่าน ER Diagram จริง — คลินิกสัตวแพทย์
นี่คือ diagram ที่ใช้ในแบบฝึกหัดวันที่ 19 ส.ค. (20 คะแนน) — ลองอ่านให้ออกก่อนดูเฉลยในบทถัดไป
อ่าน diagram นี้ให้ออก — ทีละส่วน
- Owner — owns — Pet · Owner ฝั่ง 1, Pet ฝั่ง N → 1:N · เส้นคู่ฝั่ง Pet = total participation แปลว่าสัตว์ทุกตัวต้องมีเจ้าของ
- Owner มี address เป็น composite attribute (street, city, zip เยื้องเข้ามาข้างใต้) → ต้องแตกเป็นคอลัมน์แยก ไม่ใช่สร้างตารางใหม่
- Pet — treats — Vet · ฝั่ง Pet เป็น M, ฝั่ง Vet เป็น N → M:N · relationship นี้มี attribute ของตัวเอง (treat_date, diagnosis ในวงรีด้านบน) → ต้องสร้างตารางใหม่
- Pet — has — Vaccination · Vaccination วาดเป็นกรอบสองชั้น = weak entity ·
dose_noขีดเส้นใต้ประ = discriminator - PK ที่ขีดเส้นใต้: owner_id, pet_id, vet_id
11.7เช็กลิสต์ก่อนสอบ
- วาดกรอบสี่เหลี่ยมให้ครบทุก entity
- วาดข้าวหลามตัดเชื่อม พร้อมตั้งชื่อ relationship (คนลืมบ่อย)
- เขียน cardinality (1 / N / M) ทั้งสองฝั่ง
- ใช้ เส้นคู่ ตรงฝั่งที่ total participation
- ขีดเส้นใต้ PK ทุกตัว
- weak entity ใช้กรอบสองชั้น + ข้าวหลามตัดสองชั้น + เส้นใต้ประที่ discriminator
- มีหลายค่าต่อพ่อแม่หนึ่งตัวไหม → entity
- มี attribute ย่อยของตัวเองไหม → entity
- มี id / ตัวตนของตัวเองไหม → entity
- เป็นลำดับชั้น / อ้างถึงตัวเองไหม → entity
- เป็นค่าเดี่ยว atomic ค่าเดียว → attribute
- เขียนเหตุผลสั้น ๆ ด้วยเสมอ — ข้อสอบให้คะแนนที่เหตุผล