Converting ER Diagram to Relational Schema
กฎการแปลงทั้งหมด entity · attribute · 1:1 · 1:N · M:N · ternary · participation · weak entity พร้อมตัวอย่างคลินิกสัตวแพทย์ทั้งข้อ
วิดีโอที่อาจารย์ให้ดูก่อนเรียนบทนี้:
12.1ตารางกฎการแปลง — ท่องให้ได้ทั้งตาราง
นี่คือสไลด์สรุปของอาจารย์ทั้งแผ่น ถ้าจำได้ครบ ทำข้อสอบส่วน Database Design ได้เกือบหมด
| ER Diagram Component | Relational Schema Mapping |
|---|---|
| Entity | ตาราง โดย attribute กลายเป็นคอลัมน์ |
| Attribute | Simple: 1 คอลัมน์ · Composite: แตกเป็นหลายคอลัมน์ |
| Multi-valued Attribute | สร้างตารางใหม่ ที่มี FK กลับไปหา entity เจ้าของ — เก็บหลายค่าในช่องเดียวละเมิด 1NF
(บทที่ 4 หัวข้อ 4.3) ต่างจาก composite: composite แตกเป็นหลายคอลัมน์ในตารางเดิม · multi-valued แยกเป็นตารางใหม่ |
| Binary Relationship (1:1) | Foreign key ในตารางใดตารางหนึ่ง พร้อม UNIQUE constraint |
| Binary Relationship (1:N) | Foreign key ในตารางฝั่ง "many" |
| Binary Relationship (M:N) | สร้าง junction table ที่มี foreign key จาก entity ทั้งสองฝั่ง |
| Ternary Relationship | สร้างตารางใหม่ที่มี foreign key จาก ทั้งสาม entity |
| Total Participation | Foreign key พร้อม NOT NULL constraint |
| Partial Participation | Foreign key ที่อนุญาตให้เป็น NULL |
| Weak Entity | สร้างตารางที่มี foreign key อ้างถึง owner entity และใช้ composite PK |
| Unary / Recursive Relationship (สไลด์เขียนว่า “Direct Relationship”) |
entity ที่สัมพันธ์กับตัวเอง → ใส่ FK ไว้ในตารางเดียวกัน ชี้กลับไปที่ PK ของตัวมันเอง เช่น พนักงานมีหัวหน้าซึ่งก็เป็นพนักงาน → Employee(EmpID, Name, ManagerID)
โดย ManagerID เป็น FK ชี้ไป Employee.EmpID — ต้องยอมให้เป็น NULL ไม่งั้น CEO ที่ไม่มีหัวหน้าจะ insert ไม่ได้ |
| Relationship Attributes | ใส่ไว้คู่กับ foreign key (1:N) หรือใน junction table (M:N) |
12.2Entity และ Attribute
การแปลง: ทุก entity type กลายเป็น relation (ตาราง) และ attribute ของมันกลายเป็นคอลัมน์ในตารางนั้น · primary key ที่ปกติขีดเส้นใต้ใน ER diagram กลายเป็น primary key ใน relational schema
-- ER: Student(ID, Name, DOB, Major)
Student(ID, Name, DOB, Major)
CREATE TABLE Student (
ID INT PRIMARY KEY,
Name VARCHAR(100),
DOB DATE,
Major VARCHAR(50)
);
Attribute แบ่งเป็นสองชนิด:
- Simple attribute — แมปตรง ๆ เป็นคอลัมน์
- Composite attribute — ต้องแตกออกเป็นคอลัมน์แยกสำหรับแต่ละส่วนประกอบ
-- Address เป็น composite attribute: Street, City, State, ZipCode
CREATE TABLE Student (
ID INT PRIMARY KEY,
Name VARCHAR(100),
Street VARCHAR(100),
City VARCHAR(50),
State VARCHAR(50),
ZipCode VARCHAR(10)
);
12.3Binary Relationships ทั้ง 4 แบบ
a) One-to-One (1:1)
การแปลง: เพิ่ม foreign key ในตารางใดก็ได้ โดยอ้างถึง primary key ของอีกตาราง
-- ER: Student(ID, Name) --- has_profile (1:1) --- Profile(ProfileID, Bio)
-- Student, Profile = entity (สี่เหลี่ยม) · has_profile = relationship (ข้าวหลามตัด)
Student(ID, Name, ProfileID)
Profile(ProfileID, Bio)
-- ต้องสร้างตารางที่ถูกอ้างถึงก่อนเสมอ
CREATE TABLE Profile (
ProfileID INT PRIMARY KEY,
Bio TEXT
);
-- เพิ่ม UNIQUE เพื่อรับประกันว่าไม่มี entity สองตัวชี้ไปที่เดียวกัน
CREATE TABLE Student (
ID INT PRIMARY KEY,
Name VARCHAR(100),
ProfileID INT UNIQUE,
FOREIGN KEY (ProfileID) REFERENCES Profile(ProfileID)
);
Unique constraint: สำหรับความสัมพันธ์ 1:1 ต้อง เพิ่ม UNIQUE constraint บน foreign key
เพื่อรับประกันว่าไม่มี entity สองตัวอ้างถึงคู่ของมันตัวเดียวกัน ·
ตัวอย่างอื่น: Person(PersonID, Name, PassportNumber UNIQUE) — ไม่มีคนสองคนที่มีเลขพาสปอร์ตเดียวกัน
b) One-to-Many (1:N)
การแปลง: เพิ่ม foreign key ในตารางฝั่ง "many" โดยอ้างถึงฝั่ง "one"
-- ER: Department(DeptID, DeptName) --- (1:N) --- Student(StudentID, Name)
-- อ่านว่า: หนึ่งภาควิชามีนักศึกษาได้หลายคน · นักศึกษาหนึ่งคนสังกัดได้ภาคเดียว
Department(DeptID, DeptName)
Student(StudentID, Name, DeptID) -- FK อยู่ฝั่ง many
ใส่ที่ฝั่ง many (Student) — ได้
| StudentID | Name | DeptID |
|---|---|---|
| 6601 | Anan | CoE |
| 6602 | Beam | CoE |
| 6603 | Chada | EE |
ใส่ที่ฝั่ง one (Department) — ไม่ได้
| DeptID | DeptName | StudentID |
|---|---|---|
| CoE | Computer Eng. | 6601, 6602 ? |
| EE | Electrical Eng. | 6603 |
StudentID มีช่องเดียว →
ต้องเลือกอย่างใดอย่างหนึ่ง: ยัดหลายค่าในช่องเดียว (ละเมิด 1NF) หรือ
สร้างแถว Department ซ้ำ ๆ อันละนักศึกษา (ข้อมูล DeptName ซ้ำซ้อน + PK พัง) — ทั้งสองทางผิดหมดวิธีจำสั้น ๆ: ถามว่า “ของฝั่งนี้ 1 ตัว ผูกกับอีกฝั่งได้กี่ตัว?” — ฝั่งที่ตอบว่า “ได้ตัวเดียว” คือฝั่งที่ถือ FK
นักศึกษา 1 คน → ได้ภาคเดียว → FK อยู่ที่ Student (ซึ่งก็คือฝั่ง many นั่นเอง)
อย่าจำเป็นคำว่า “ฝั่ง 1” เฉย ๆ เพราะเลข 1 ในไดอะแกรมอาจถูกเขียนไว้คนละฝั่งตาม convention ที่ใช้ (ดูบทที่ 11.4) — แต่คำถาม “1 ตัวผูกได้กี่ตัว” ให้คำตอบเดียวกันเสมอ
c) Many-to-Many (M:N)
การแปลง: สร้าง relation ใหม่ที่มี foreign key ไปยัง entity ทั้งสอง
-- ER: Student(ID, Name) --- (M:N) --- Course(CourseID, CourseName)
Student(ID, Name)
Course(CourseID, CourseName)
Enrollment(StudentID, CourseID) -- ตารางใหม่แทนความสัมพันธ์
CREATE TABLE Enrollment (
StudentID INT,
CourseID INT,
PRIMARY KEY (StudentID, CourseID),
FOREIGN KEY (StudentID) REFERENCES Student(ID),
FOREIGN KEY (CourseID) REFERENCES Course(CourseID)
);
ลองคิดตามว่าทำไมทำแบบนั้นไม่ได้: ถ้าใส่
course_id ลงในตาราง Student นักศึกษาหนึ่งคนจะลงได้แค่วิชาเดียว
ถ้าใส่ student_id ลงใน Course วิชาหนึ่งจะมีนักศึกษาได้แค่คนเดียว — ทั้งสองแบบทำลายความเป็น M:N
d) Many-to-One (N:1)
ในความสัมพันธ์ many-to-one entity หลายตัวในเซตหนึ่ง สัมพันธ์กับ entity ไม่เกินหนึ่งตัวในอีกเซตหนึ่ง · กฎการแปลงเหมือน 1:N คือใส่ FK ที่ฝั่ง "many"
-- ER: Employee(EmployeeID, Name) --- (N:1) --- Department(DepartmentID, DeptName)
Employee(EmployeeID, Name, DepartmentID)
Department(DepartmentID, DeptName)
ทำให้พนักงานหลายคนสังกัดแผนกเดียวกันได้
12.4Ternary Relationship
| ชื่อ | จำนวน entity | ตัวอย่าง |
|---|---|---|
| Unary (recursive) | 1 | พนักงาน เป็นหัวหน้าของ พนักงาน |
| Binary | 2 | นักศึกษา ลงทะเบียน รายวิชา — พบบ่อยที่สุด |
| Ternary | 3 | ซัพพลายเออร์ ส่ง ชิ้นส่วน ให้ โปรเจกต์ |
“Acme ส่ง น็อต ให้ โปรเจกต์สะพาน จำนวน 500 ตัว”
ถ้าแตกเป็น binary 3 อัน (Acme–น็อต, น็อต–สะพาน, Acme–สะพาน) เราจะเสียข้อมูลไป — รู้แค่ว่า Acme ขายน็อต และสะพานใช้น็อต และ Acme ทำงานกับสะพาน แต่ตอบไม่ได้ว่าน็อตที่สะพานใช้นั้นมาจาก Acme หรือจากซัพพลายเออร์อื่น
รูปสี่เหลี่ยมข้าวหลามตัด = relationship · มีเส้นออกไป 3 เส้น = ternary · Quantity คือ attribute ของ relationship
การแปลง: สร้าง relation ใหม่ที่มี foreign key ไปยัง entity ที่เกี่ยวข้องทั้งหมด พร้อม attribute ของ relationship · PK ของตารางใหม่ = FK ทั้งสามรวมกัน
-- ER: Supplier / Part / Project ทั้งสามเชื่อมเข้าข้าวหลามตัดเดียวชื่อ Supplies
-- (ternary — ไม่ใช่ binary สามอันมาต่อกัน) · attribute ของ relationship = Quantity
Supplier(SupplierID, SupplierName)
Part(PartID, PartName)
Project(ProjectID, ProjectName)
Supplies(SupplierID, PartID, ProjectID, Quantity)
CREATE TABLE Supplies (
SupplierID INT,
PartID INT,
ProjectID INT,
Quantity INT,
PRIMARY KEY (SupplierID, PartID, ProjectID),
FOREIGN KEY (SupplierID) REFERENCES Supplier(SupplierID),
FOREIGN KEY (PartID) REFERENCES Part(PartID),
FOREIGN KEY (ProjectID) REFERENCES Project(ProjectID)
);
12.5Participation Constraints
-- Employee ---(Total)--- Works_In --- Department
-- พนักงานทุกคนต้องสังกัดแผนก
Employee(EmpID, Name, DeptID NOT NULL)
Department(DeptID, DeptName)
NOT NULL constraint รับประกันว่าพนักงานทุกคนต้องผูกกับแผนก
-- Employee ---(Partial)--- Assigned to --- Project
-- ไม่ใช่พนักงานทุกคนที่ถูกมอบหมายโปรเจกต์
Employee(EmpID, Name, ProjectID)
ปล่อยให้ FK เป็น NULL ได้ — ไม่ต้องเขียนอะไรเพิ่ม เพราะเป็นค่าเริ่มต้นอยู่แล้ว
ถ้าเห็นเส้นคู่ใน ER diagram ต้องเขียน NOT NULL ตรง FK ทันที
12.6Weak Entity
การแปลง: สร้างตารางสำหรับ weak entity ที่มีpartial key ของมันและforeign key ไปยัง owner entity
-- ER: Employee(EmpID, Name) --- Owns --- Dependent(Name, Relationship)
Employee(EmpID, Name)
Dependent(EmpID, Name, Relationship)
CREATE TABLE Dependent (
EmpID INT,
Name VARCHAR(50),
Relationship VARCHAR(30),
PRIMARY KEY (EmpID, Name),
FOREIGN KEY (EmpID) REFERENCES Employee(EmpID)
);
primary key ของ Dependent คือการรวมกันของ partial key (Name) กับ foreign key ไปยัง owner (EmpID)
PRIMARY KEY (dose_no)) หรือสร้าง id ใหม่ให้ weak entity
(เช่น vaccination_id INT AUTO_INCREMENT PRIMARY KEY) — ทั้งสองแบบผิดweak entity ต้องใช้ composite PK = (PK ของ owner, discriminator)
12.7Relationship Attributes และ Denormalization
ถ้า relationship มี attribute ของตัวเอง ให้ใส่ไว้คู่กับ foreign key
-- ER: Employee --- (N:1, JoinDate) --- Department
Employee(EmployeeID, Name, DepartmentID, JoinDate)
Department(DepartmentID, DeptName)
Employee(EmployeeID, Name, DepartmentID, DeptName)
Department(DepartmentID, DeptName)
Denormalization แบบนี้เร่ง query ที่ต้องใช้ทั้งข้อมูลพนักงานและแผนกพร้อมกัน แต่แลกมาด้วย data redundancy
(ถ้าเปลี่ยนชื่อแผนก ต้องไล่แก้ทุกแถวของพนักงาน) — ในข้อสอบอย่าทำ ถ้าโจทย์ไม่ได้ขอ
12.8เฉลยเต็ม — คลินิกสัตวแพทย์ (20 คะแนน)
โจทย์: จากไดอะแกรมคลินิกสัตวแพทย์ที่ให้มา จงแปลงเป็น relational schema โดยเขียน ชื่อตาราง, attribute พร้อมชนิดข้อมูล (MySQL), PK, FK และ constraints ให้ครบทุกตาราง
เฉลยเต็มทั้ง 5 ตาราง
CREATE TABLE Owner (
owner_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
street VARCHAR(100), -- composite attribute แตกเป็น 3 คอลัมน์
city VARCHAR(50),
zip VARCHAR(10)
);
CREATE TABLE Pet (
pet_id INT PRIMARY KEY,
pet_name VARCHAR(50) NOT NULL,
species VARCHAR(50),
owner_id INT NOT NULL, -- total participation → NOT NULL
FOREIGN KEY (owner_id) REFERENCES Owner(owner_id)
);
CREATE TABLE Vet (
vet_id INT PRIMARY KEY,
vet_name VARCHAR(100) NOT NULL,
specialty VARCHAR(50)
);
-- M:N ระหว่าง Pet กับ Vet + relationship attributes
CREATE TABLE Treatment (
pet_id INT,
vet_id INT,
treat_date DATE,
diagnosis VARCHAR(255),
PRIMARY KEY (pet_id, vet_id, treat_date),
FOREIGN KEY (pet_id) REFERENCES Pet(pet_id),
FOREIGN KEY (vet_id) REFERENCES Vet(vet_id)
);
-- weak entity ที่ Pet เป็นเจ้าของ
CREATE TABLE Vaccination (
pet_id INT,
dose_no INT, -- discriminator (partial key)
vaccine_name VARCHAR(50) NOT NULL,
PRIMARY KEY (pet_id, dose_no),
FOREIGN KEY (pet_id) REFERENCES Pet(pet_id)
);
• ชนิดข้อมูลรับรูปแบบสมเหตุผลอื่นได้ (เช่น zip เป็น
CHAR(5), diagnosis เป็น TEXT)• FK ของ weak entity และ junction table เป็น NOT NULL โดยปริยาย เพราะเป็นส่วนของ PK (ใน SQL คอลัมน์ที่อยู่ใน PRIMARY KEY จะเป็น NOT NULL อัตโนมัติ)
รายการจุดหักคะแนนที่พบบ่อย — อ่านก่อนเข้าห้องสอบ
| หัก | สิ่งที่ทำผิด | สะท้อนว่าไม่เข้าใจอะไร |
|---|---|---|
| −4 | ใส่ FK แทนการสร้างตาราง Treatment | สับสน M:N กับ 1:N |
| −2 | สร้างตาราง Address แยกออกมา | สับสน composite attribute กับ multi-valued |
| −2 | Vaccination ใช้ dose_no เดี่ยวเป็น PK หรือสร้าง id ใหม่ | ไม่เข้าใจ weak entity / discriminator |
| −2 | treat_date ไม่อยู่ใน PK | ไม่ได้คิดว่าคู่เดิมพบกันซ้ำได้ |
| −1 | ลืม NOT NULL ที่ owner_id | ไม่เข้าใจ total participation |
12.9ขั้นตอนทำข้อสอบแปลง ER → Schema
- ไล่ entity ทุกตัวก่อน → เขียนตารางว่างพร้อม PK ที่ขีดเส้นใต้ใน diagram
- ดู attribute ทีละตัว → composite แตกเป็นหลายคอลัมน์ · multi-valued แยกตารางใหม่
- ไล่ relationship ทีละเส้น อ่าน cardinality ที่เขียนกำกับ
- 1:1 → FK ฝั่งใดก็ได้ + UNIQUE
- 1:N / N:1 → FK ฝั่ง many
- M:N → ตารางใหม่ PK = FK ทั้งสอง
- ternary → ตารางใหม่ FK ทั้งสาม
- มองหาเส้นคู่ → เติม NOT NULL ที่ FK ฝั่งนั้น
- มองหากรอบสองชั้น → weak entity → PK = (PK owner, discriminator)
- มองหาวงรีที่ห้อยจาก relationship → relationship attribute → ใส่ในตารางที่แทน relationship นั้น
- ถามตัวเองว่า “คู่เดิมเกิดซ้ำได้ไหม” → ถ้าได้ ต้องมีคอลัมน์วันที่/ลำดับอยู่ใน PK ด้วย
- ตรวจรอบสุดท้าย — ทุกตารางมี PK ครบไหม · ทุก FK ชี้ไป PK จริงไหม · ชนิดข้อมูลสมเหตุสมผลไหม
treat_date และ diagnosis · ควรแปลงอย่างไรตัวเลือกสุดท้ายไม่ใช่คำตอบที่เฉลยต้องการ เพราะเฉลยระบุ
PRIMARY KEY (pet_id, vet_id, treat_date) ชัดเจน (การใส่ surrogate key เพิ่มโดยไม่มี UNIQUE บนสามคอลัมน์นั้น ยังยอมให้บันทึกซ้ำได้)dose_no · PRIMARY KEY ควรเป็นอะไรdose_no เดี่ยว ๆ ไม่พอเพราะสัตว์คนละตัวมี dose_no ซ้ำกันได้ · การสร้าง id ใหม่ทำให้ความหมายของ weak entity หายไป (โดนหัก 2 คะแนน)