EN813304 · Midterm
บทที่ 12 / 12 · erdiagram2schema.pptx · 19 ส.ค.

Converting ER Diagram to Relational Schema

กฎการแปลงทั้งหมด entity · attribute · 1:1 · 1:N · M:N · ternary · participation · weak entity พร้อมตัวอย่างคลินิกสัตวแพทย์ทั้งข้อ

Design 15%ออกสอบแน่
หัวข้อในบทนี้

วิดีโอที่อาจารย์ให้ดูก่อนเรียนบทนี้:

Converting ER Diagram to Relational Schema

12.1ตารางกฎการแปลง — ท่องให้ได้ทั้งตาราง

นี่คือสไลด์สรุปของอาจารย์ทั้งแผ่น ถ้าจำได้ครบ ทำข้อสอบส่วน Database Design ได้เกือบหมด

ER Diagram ComponentRelational Schema Mapping
Entityตาราง โดย attribute กลายเป็นคอลัมน์
AttributeSimple: 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 ParticipationForeign key พร้อม NOT NULL constraint
Partial ParticipationForeign 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 แบ่งเป็นสองชนิด:

-- 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)
);
จุดหักคะแนน −2 สร้างตาราง Address แยกออกมา — ผิด · composite attribute ต้องแตกเป็นคอลัมน์ในตารางเดิม ไม่ใช่แยกเป็นตารางใหม่ (การแยกตารางใช้กับ multi-valued attribute ต่างหาก)

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
ทำไม FK ต้องอยู่ฝั่ง “many” — เหตุผลที่ต้องอธิบายได้ในข้อสอบ เพราะช่องหนึ่งช่องเก็บได้ค่าเดียว — FK จึงใส่ได้เฉพาะฝั่งที่ “ชี้ไปหาอีกฝั่งได้แค่ตัวเดียว”

ใส่ที่ฝั่ง many (Student) — ได้
StudentIDNameDeptID
6601AnanCoE
6602BeamCoE
6603ChadaEE
นักศึกษาแต่ละคนสังกัดภาคเดียว → เก็บค่าเดียวพอ · ภาค CoE มีนักศึกษาหลายคนก็ไม่มีปัญหา เพราะค่าซ้ำในคอลัมน์ FK ได้

ใส่ที่ฝั่ง one (Department) — ไม่ได้
DeptIDDeptNameStudentID
CoEComputer Eng.6601, 6602 ?
EEElectrical Eng.6603
ภาค CoE มีนักศึกษา 2 คน แต่ช่อง 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)
);
จุดหักคะแนน −4 (หนักที่สุด) ใส่ FK ในตารางใดตารางหนึ่งแทนการสร้าง junction table — สะท้อนว่าสับสนระหว่าง M:N กับ 1:N
ลองคิดตามว่าทำไมทำแบบนั้นไม่ได้: ถ้าใส่ 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

Ternary คืออะไร — นับจาก “มี entity กี่ตัวร่วมในความสัมพันธ์เดียว”
ชื่อจำนวน entityตัวอย่าง
Unary (recursive)1พนักงาน เป็นหัวหน้าของ พนักงาน
Binary2นักศึกษา ลงทะเบียน รายวิชา — พบบ่อยที่สุด
Ternary3ซัพพลายเออร์ ส่ง ชิ้นส่วน ให้ โปรเจกต์
ทำไม ternary ถึงไม่ใช่ binary สามอันมาต่อกัน — เพราะข้อเท็จจริงที่เก็บคือสามอย่างพร้อมกัน:
“Acme ส่ง น็อต ให้ โปรเจกต์สะพาน จำนวน 500 ตัว”
ถ้าแตกเป็น binary 3 อัน (Acme–น็อต, น็อต–สะพาน, Acme–สะพาน) เราจะเสียข้อมูลไป — รู้แค่ว่า Acme ขายน็อต และสะพานใช้น็อต และ Acme ทำงานกับสะพาน แต่ตอบไม่ได้ว่าน็อตที่สะพานใช้นั้นมาจาก Acme หรือจากซัพพลายเออร์อื่น
Supplier Project Part Supplies Quantity

รูปสี่เหลี่ยมข้าวหลามตัด = 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

Total Participation → NOT NULL
-- Employee ---(Total)--- Works_In --- Department
-- พนักงานทุกคนต้องสังกัดแผนก
Employee(EmpID, Name, DeptID NOT NULL)
Department(DeptID, DeptName)

NOT NULL constraint รับประกันว่าพนักงานทุกคนต้องผูกกับแผนก

Partial Participation → อนุญาต NULL
-- Employee ---(Partial)--- Assigned to --- Project
-- ไม่ใช่พนักงานทุกคนที่ถูกมอบหมายโปรเจกต์
Employee(EmpID, Name, ProjectID)

ปล่อยให้ FK เป็น NULL ได้ — ไม่ต้องเขียนอะไรเพิ่ม เพราะเป็นค่าเริ่มต้นอยู่แล้ว

จุดหักคะแนน −1 ลืมใส่ NOT NULL ที่ owner_id — สะท้อนว่าไม่เข้าใจ total participation
ถ้าเห็นเส้นคู่ใน 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)

จุดหักคะแนน −2 ใช้ discriminator เดี่ยว ๆ เป็น PK (เช่น 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)
Performance และ Denormalization พิจารณาว่าตารางไหนจะถูก query บ่อยกว่า · ในบางกรณีอาจเพิ่มข้อมูลซ้ำซ้อนเพื่อเร่ง query ที่ใช้บ่อย
Employee(EmployeeID, Name, DepartmentID, DeptName)
Department(DepartmentID, DeptName)
Denormalization แบบนี้เร่ง query ที่ต้องใช้ทั้งข้อมูลพนักงานและแผนกพร้อมกัน แต่แลกมาด้วย data redundancy (ถ้าเปลี่ยนชื่อแผนก ต้องไล่แก้ทุกแถวของพนักงาน) — ในข้อสอบอย่าทำ ถ้าโจทย์ไม่ได้ขอ

12.8เฉลยเต็ม — คลินิกสัตวแพทย์ (20 คะแนน)

โจทย์: จากไดอะแกรมคลินิกสัตวแพทย์ที่ให้มา จงแปลงเป็น relational schema โดยเขียน ชื่อตาราง, attribute พร้อมชนิดข้อมูล (MySQL), PK, FK และ constraints ให้ครบทุกตาราง

ER Diagram คลินิกสัตวแพทย์

เฉลยเต็มทั้ง 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)
);
หมายเหตุจากเฉลยของอาจารย์ • treat_date อยู่ใน PK เพราะสัตว์ตัวเดิม–สัตวแพทย์คนเดิมพบกันได้หลายครั้ง
• ชนิดข้อมูลรับรูปแบบสมเหตุผลอื่นได้ (เช่น 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
−2Vaccination ใช้ dose_no เดี่ยวเป็น PK หรือสร้าง id ใหม่ไม่เข้าใจ weak entity / discriminator
−2treat_date ไม่อยู่ใน PKไม่ได้คิดว่าคู่เดิมพบกันซ้ำได้
−1ลืม NOT NULL ที่ owner_idไม่เข้าใจ total participation

12.9ขั้นตอนทำข้อสอบแปลง ER → Schema

  1. ไล่ entity ทุกตัวก่อน → เขียนตารางว่างพร้อม PK ที่ขีดเส้นใต้ใน diagram
  2. ดู attribute ทีละตัว → composite แตกเป็นหลายคอลัมน์ · multi-valued แยกตารางใหม่
  3. ไล่ relationship ทีละเส้น อ่าน cardinality ที่เขียนกำกับ
    • 1:1 → FK ฝั่งใดก็ได้ + UNIQUE
    • 1:N / N:1 → FK ฝั่ง many
    • M:N → ตารางใหม่ PK = FK ทั้งสอง
    • ternary → ตารางใหม่ FK ทั้งสาม
  4. มองหาเส้นคู่ → เติม NOT NULL ที่ FK ฝั่งนั้น
  5. มองหากรอบสองชั้น → weak entity → PK = (PK owner, discriminator)
  6. มองหาวงรีที่ห้อยจาก relationship → relationship attribute → ใส่ในตารางที่แทน relationship นั้น
  7. ถามตัวเองว่า “คู่เดิมเกิดซ้ำได้ไหม” → ถ้าได้ ต้องมีคอลัมน์วันที่/ลำดับอยู่ใน PK ด้วย
  8. ตรวจรอบสุดท้าย — ทุกตารางมี PK ครบไหม · ทุก FK ชี้ไป PK จริงไหม · ชนิดข้อมูลสมเหตุสมผลไหม
Pet — treats — Vet เป็นความสัมพันธ์ M:N ที่มี attribute treat_date และ diagnosis · ควรแปลงอย่างไร
M:N ต้องสร้าง junction table (ตัวเลือก 1 และ 2 ผิด −4 คะแนน) · และ treat_date ต้องอยู่ใน PK เพราะคู่ pet–vet เดิมพบกันได้หลายครั้ง
ตัวเลือกสุดท้ายไม่ใช่คำตอบที่เฉลยต้องการ เพราะเฉลยระบุ PRIMARY KEY (pet_id, vet_id, treat_date) ชัดเจน (การใส่ surrogate key เพิ่มโดยไม่มี UNIQUE บนสามคอลัมน์นั้น ยังยอมให้บันทึกซ้ำได้)
ER diagram แสดง เส้นคู่ระหว่าง Pet กับ relationship "owns" · เมื่อแปลงเป็น schema ต้องทำอะไร
เส้นคู่ = total participation → FK เป็น NOT NULL · ลืมข้อนี้โดนหัก 1 คะแนน · UNIQUE ใช้กับ 1:1 · การสร้างตารางใหม่ใช้กับ M:N
Vaccination เป็น weak entity ของ Pet มี discriminator คือ dose_no · PRIMARY KEY ควรเป็นอะไร
weak entity ใช้ composite PK = (PK ของ owner, discriminator) · dose_no เดี่ยว ๆ ไม่พอเพราะสัตว์คนละตัวมี dose_no ซ้ำกันได้ · การสร้าง id ใหม่ทำให้ความหมายของ weak entity หายไป (โดนหัก 2 คะแนน)
ระบบจำไว้ในเบราว์เซอร์นี้ จะได้รู้ว่าอ่านถึงไหนแล้ว