Relational Model & Keys
relation / tuple / attribute / domain · superkey · candidate key · primary key · foreign key และ referential integrity
วิดีโอที่อาจารย์ให้ดูก่อนเรียนบทนี้:
2.1Relational Model คืออะไร
Relational model คือกรอบทฤษฎีสำหรับแทนและจัดการข้อมูล เสนอโดย E.F. Codd ในปี 1970 และเป็นรากฐานของฐานข้อมูลเชิงสัมพันธ์ทุกตัวที่เราใช้ทุกวันนี้
- ข้อมูลถูกจัดเป็น relations (ตาราง)
- แต่ละ relation ประกอบด้วย tuples (แถว) และ attributes (คอลัมน์)
- ความสัมพันธ์ระหว่างข้อมูลถูกแทนด้วย attribute ที่ใช้ร่วมกัน — ไม่ใช่ pointer หรือลิงก์แบบโมเดลรุ่นเก่า
กายวิภาคของ relation
| ID | name | dept_name | salary |
|---|---|---|---|
| 10101 | Srinivasan | Comp. Sci. | 65000 |
| 12121 | Wu | Finance | 90000 |
| 15151 | Mozart | Music | 40000 |
| คำศัพท์ | ความหมาย |
|---|---|
| Relation | ตาราง |
| Tuple | แถว 1 แถว = element t ของ relation r |
| Attribute | คอลัมน์ |
| Domain | เซตของค่าที่อนุญาตสำหรับ attribute นั้น |
| Arity | จำนวน attribute (จำนวนคอลัมน์) |
| Cardinality | จำนวน tuple (จำนวนแถว) |
| Relation schema | R = (A₁, A₂, …, Aₙ) เช่น instructor = (ID, name, dept_name, salary) |
| Relation instance | r(R) = ค่าจริงในตาราง ณ ขณะหนึ่ง |
ในนิยามเชิงคณิตศาสตร์: ให้เซต D₁, D₂, …, Dₙ · relation r คือ สับเซตของ D₁ × D₂ × … × Dₙ
(เครื่องหมาย × ตรงนี้คือ Cartesian product = การจับคู่ทุกค่าจากทุก domain เข้าด้วยกัน ไม่ใช่การคูณเลข — เรียนเต็ม ๆ ในบทที่ 3)
ดังนั้น relation จึงเป็น เซตของ n-tuple (a₁, a₂, …, aₙ) โดยที่ aᵢ ∈ Dᵢ
2. ค่าพิเศษ null เป็นสมาชิกของทุก domain แปลว่า “ไม่ทราบค่า” · null สร้างความยุ่งยากให้นิยามของ operation หลายตัว (ดูบทที่ 7)
3. ลำดับของ tuple ไม่มีความหมาย — relation คือเซต ระบบจะเก็บเรียงยังไงก็ได้ และห้ามมี tuple สองแถวที่ค่าเหมือนกันทุก attribute
2.2Keys — หัวใจของบทนี้
เราต้องมีวิธีระบุว่า tuple แต่ละแถวใน relation ต่างกันอย่างไร ซึ่งแสดงออกผ่านค่าของ attribute ค่าเหล่านั้นต้องระบุ tuple ได้เฉพาะตัว และตามกฎข้อ 3 ด้านบน จะไม่มี tuple สองแถวที่มีค่าเหมือนกันทั้งหมด
Superkey
Superkey คือ เซตของ attribute ตั้งแต่หนึ่งตัวขึ้นไปที่เมื่อรวมกันแล้วสามารถระบุ tuple ได้เฉพาะตัว
R เป็นเซตของ attribute ใน schema ของ relation rสับเซต
K ⊆ R เป็น superkey ของ r ก็ต่อเมื่อ:
ถ้า t₁ และ t₂ อยู่ใน r และ t₁ ≠ t₂ แล้ว t₁.K ≠ t₂.Kพูดง่าย ๆ: ไม่มี tuple สองแถวที่มีค่าเหมือนกันบน attribute ทุกตัวใน K
ตัวอย่าง: ID เพียงตัวเดียวก็แยกอาจารย์แต่ละคนได้แล้ว → ID เป็น superkey ·
ส่วน name ไม่ใช่ superkey เพราะอาจารย์หลายคนอาจชื่อเหมือนกัน
Candidate key
superkey อาจมี attribute ส่วนเกิน ติดมาด้วย เช่น {ID, name} ก็เป็น superkey ของ instructor
เพราะถ้า K เป็น superkey แล้ว ทุก superset ของ K ก็เป็น superkey ด้วย
Candidate key = superkey ที่ minimal คือ superkey ที่ไม่มีสับเซตแท้ตัวใดเป็น superkey ได้อีก
{name, dept_name} รวมกันแยกอาจารย์ได้ ดังนั้น ทั้ง {ID} และ {name, dept_name} เป็น candidate keyแต่
{ID, name} ไม่ใช่ candidate key ถึงแม้จะแยก tuple ได้ก็ตาม — เพราะ ID เพียงตัวเดียวก็เป็น candidate key อยู่แล้ว
จึงตัด name ทิ้งได้ → ไม่ minimal
Primary key
Primary key คือ candidate key ที่ผู้ออกแบบฐานข้อมูลเลือก ให้เป็นวิธีหลักในการระบุ tuple
- ควรเลือกตัวที่ค่าไม่เคยเปลี่ยน หรือเปลี่ยนน้อยมาก
- เช่น ไม่ควรเอา address มาเป็นส่วนหนึ่งของ primary key เพราะมีโอกาสเปลี่ยนสูง
- ทำไมการเปลี่ยนค่า PK ถึงแย่ — ทุก foreign key ในตารางอื่นที่ชี้มาที่ค่านี้ ต้องถูกแก้ตามให้ครบในคราวเดียว ถ้าแก้ไม่ครบ แถวที่ชี้มาจะกลายเป็น orphan (ชี้ไปยังค่าที่ไม่มีอยู่แล้ว) ซึ่งละเมิด referential integrity
- ธรรมเนียม: เขียน attribute ของ primary key ไว้หน้าสุด และขีดเส้นใต้
ตัวอย่างจากสไลด์
Students(StudentID, FirstName, LastName, DateOfBirth, Email, PhoneNumber)
Superkeys: {StudentID}, {Email}, {StudentID, FirstName}, {StudentID, Email}, … ชุดใดก็ตามที่มี StudentID หรือ Email อยู่
Candidate keys: {StudentID}, {Email}
Primary key: StudentID
เหตุผล: StudentID ระบบออกให้และรับประกันว่าไม่ซ้ำ · Email เป็น candidate key ได้ แต่นักศึกษาเปลี่ยนอีเมลได้
Employees(EmployeeID, SocialSecurityNumber, FirstName, LastName, Department, Position, HireDate)
Candidate keys: {EmployeeID}, {SocialSecurityNumber}
Primary key: EmployeeID
เหตุผล: ทั้งคู่ระบุพนักงานได้เฉพาะตัว แต่เลือก EmployeeID เพราะระบบสร้างให้เอง และการใช้เลขประกันสังคมมีประเด็นเรื่องความเป็นส่วนตัว
2.3Foreign Key และ Referential Integrity
relation r₁ อาจมี attribute ที่เป็น primary key ของอีก relation หนึ่ง คือ r₂
attribute นั้นเรียกว่า foreign key จาก r₁ อ้างอิงไปยัง r₂
r₁= referencing relation (ตารางที่อ้างอิงคนอื่น — ที่ FK อยู่)r₂= referenced relation (ตารางที่ถูกอ้างถึง — ที่ PK อยู่)
ตัวอย่างในสไลด์: attribute dept_name ใน instructor เป็น foreign key อ้างอิง department
เพราะ dept_name เป็น primary key ของ department
| dept_name PK | building | budget |
|---|---|---|
| Comp. Sci. | Taylor | 100000 |
| Finance | Painter | 120000 |
| Music | Packard | 80000 |
| ID PK | name | dept_name FK |
|---|---|---|
| 10101 | Srinivasan | Comp. Sci. |
| 12121 | Wu | Finance |
| 15151 | Mozart | Music |
2. ป้องกันการใส่ข้อมูลที่ไม่ถูกต้องลงในคอลัมน์ FK เพราะค่าต้องเป็นหนึ่งในค่าที่มีอยู่ในตารางที่มันชี้ไป
ตัวอย่างต่อไปใช้ตาราง Enrollment ซึ่งมีหน้าตาแบบนี้ (จะอธิบายเรื่อง composite key ในหัวข้อ 2.4):
CREATE TABLE Enrollment (
student_id CHAR(10) NOT NULL,
course_id CHAR(8) NOT NULL,
semester CHAR(1) NOT NULL,
year YEAR NOT NULL,
grade CHAR(2),
PRIMARY KEY (student_id, course_id, semester, year),
FOREIGN KEY (student_id) REFERENCES Student(student_id),
FOREIGN KEY (course_id) REFERENCES Course(course_id)
);
-- ค่าที่ใส่ใน INSERT เรียงตามลำดับคอลัมน์นี้:
-- student_id, course_id, semester, year, grade
ก่อนจะทดลอง insert ต้องรู้ก่อนว่าตารางแม่ทั้งสองมีแถวอะไรอยู่บ้าง เพราะ FK จะเช็คกับแถวเหล่านี้:
| student_id | name |
|---|---|
| 6401001001 | Anan |
| 6401001002 | Beam |
| 6401001003 | Chada |
ไม่มี 9999999999
| course_id | title |
|---|---|
| EN813304 | Database Systems |
| EN811001 | Programming I |
-- ผิด: student_id 9999999999 ไม่มีอยู่ในตาราง Student
INSERT INTO Enrollment VALUES ('9999999999', 'EN813304', '1', 2026, 'A');
-- ERROR 1452: Cannot add or update a child row:
-- a foreign key constraint fails
-- ถูก: student_id 6401001001 มีอยู่จริง → ผ่าน constraint
INSERT INTO Enrollment VALUES ('6401001001', 'EN813304', '1', 2026, 'B');
-- (แต่ถ้าคีย์ชุดนี้ซ้ำกับแถวเดิม จะติด PRIMARY KEY แทน)
CREATE TABLE Departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL
);
CREATE TABLE Employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(50) NOT NULL,
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
);
-- ใน Departments มีอยู่แค่ 2 แถว: (1, 'HR') และ (2, 'IT')
a) INSERT INTO Employees VALUES (101,'John Doe', 2); → สำเร็จ เพราะ dept_id = 2 มีอยู่จริงใน Departmentsb)
INSERT INTO Employees VALUES (102,'Jane Smith', 4); → ล้มเหลว เพราะ dept_id = 4 ไม่มีใน Departments ทำให้ผิด foreign key constraint
กรณีที่ประกาศ foreign key ไม่ได้ — ตัวอย่าง section / teaches
ก่อนอื่นต้องรู้จักสองตารางนี้ก่อน (จะใช้ต่อในบทที่ 3 และ 9 ด้วย):
| course_id PK | sec_id PK | semester PK | year PK |
|---|---|---|---|
| CS-101 | 1 | Fall | 2017 |
| CS-101 | 2 | Fall | 2017 |
| CS-347 | 1 | Fall | 2017 |
ต้องใช้ครบ 4 คอลัมน์ถึงระบุได้ว่าเป็นการเปิดสอนครั้งไหน
| ID | course_id | sec_id | semester | year |
|---|---|---|---|---|
| 10101 | CS-101 | 1 | Fall | 2017 |
| 45565 | CS-101 | 1 | Fall | 2017 |
| 83821 | CS-347 | 1 | Fall | 2017 |
สองแถวบนมี (course_id, sec_id, semester, year) ซ้ำกัน เพราะอาจารย์สองคนสอน section เดียวกัน
ทีนี้สมมติเราอยากบังคับว่า “ถ้ามี section ของรายวิชาหนึ่งอยู่ ต้องมีอาจารย์สอนอย่างน้อยหนึ่งคน” แต่ก็เป็นไปได้ที่จะมีอาจารย์สอนมากกว่าหนึ่งคน
เพื่อบังคับข้อนี้ เราจะต้องกำหนดว่า ถ้าชุดค่า (course_id, sec_id, semester, year) ปรากฏใน section
ชุดเดียวกันนั้นต้องปรากฏใน teaches ด้วย แต่ชุดค่านี้ไม่ได้เป็น primary key ของ teaches
(เพราะอาจารย์หลายคนสอน section เดียวกันได้ ค่าจึงซ้ำได้)
แต่เราประกาศในทิศทางกลับกันได้ คือจาก teaches → section เพราะ (course_id, sec_id, semester, year) เป็น PK ของ section
ตัวอย่างชุด FK จากสไลด์
Customers(CustomerID, FirstName, LastName, Email, Address)
Books(BookID, Title, Author, ISBN, Price)
Orders(OrderID, CustomerID, OrderDate, TotalAmount)
OrderItems(OrderItemID, OrderID, BookID, Quantity, Price)
FK เหล่านี้รับประกันว่า ทุกออเดอร์ผูกกับลูกค้าที่มีอยู่จริง · ทุกรายการในออเดอร์อยู่ใต้ออเดอร์ที่มีจริง · และอ้างถึงหนังสือที่มีจริง
Insurance(InsuranceID, ProviderName, PolicyNumber)
Patients(PatientID, FirstName, LastName, DOB, InsuranceID)
Doctors(DoctorID, FirstName, LastName, Specialization)
Appointments(AppointmentID, PatientID, DoctorID,
AppointmentDate, AppointmentTime)
FK รับประกันว่าผู้ป่วยทุกคนผูกกับกรมธรรม์ที่มีอยู่จริง และทุกนัดหมายเชื่อมกับทั้งผู้ป่วยและแพทย์ที่มีจริง
2.4Composite Primary Key
บางตารางต้องใช้หลาย attribute รวมกันจึงจะระบุแถวได้เฉพาะตัว — จากแบบฝึกหัดในคาบ:
CREATE TABLE Enrollment (
student_id CHAR(10) NOT NULL,
course_id CHAR(8) NOT NULL,
semester CHAR(1) NOT NULL,
year YEAR NOT NULL,
grade CHAR(2),
PRIMARY KEY (student_id, course_id, semester, year),
FOREIGN KEY (student_id) REFERENCES Student(student_id),
FOREIGN KEY (course_id) REFERENCES Course(course_id)
);
ทำไม student_id เพียงตัวเดียวเป็น PK ของ Enrollment ไม่ได้
เพราะนักศึกษาหนึ่งคนลงทะเบียนได้หลายวิชา ในข้อมูลจริงมีแถว
('6401001001','EN813304',…) และ ('6401001001','EN813201',…)
ถ้าใช้ student_id เป็น PK จะเก็บได้แค่แถวเดียว ระบบจะปฏิเสธแถวที่สอง
ยิ่งกว่านั้น นักศึกษาคนเดิมลงวิชาเดิมซ้ำได้ในภาคเรียนหรือปีต่างกัน (เช่น ตก แล้วลงใหม่)
จึงต้องใส่ semester และ year เข้าไปใน PK ด้วย
(student_id, course_id, semester, year, grade) เป็น superkey ไหม เป็น candidate key ไหม
เป็น superkey — ใช่ เพราะมันมี PK อยู่ครบ จึงระบุ tuple ได้เฉพาะตัวแน่นอน (superset ของ superkey ก็ยังเป็น superkey)
เป็น candidate key หรือไม่ — ไม่ใช่ เพราะตัด grade ออกได้ แล้วยังระบุ tuple ได้เฉพาะตัวอยู่ จึงไม่ minimal
นี่คือความต่างระหว่าง superkey กับ candidate key ที่ข้อสอบชอบถาม
grade = NULL แปลว่าได้เกรด 0 หรือเปล่า
ไม่ใช่ · NULL แปลว่า “ไม่ทราบค่า” หรือ “ยังไม่มีค่า” ไม่ใช่ศูนย์และไม่ใช่สตริงว่าง
ในบริบทนี้แปลว่า ยังไม่ได้ตัดเกรด — นักศึกษาลงทะเบียนแล้วแต่ภาคเรียนยังไม่จบ หรืออาจารย์ยังไม่ส่งเกรด
ถ้าเก็บเป็น 0 จะผิดความหมาย — “ยังไม่ตัดเกรด” กับ “ได้ 0” เป็นคนละเรื่อง
และถ้าคอลัมน์เกรดเป็นตัวเลข (เช่น grade_point DECIMAL(3,2) แทน grade CHAR(2) ในตัวอย่างนี้)
การเก็บ 0 แทน NULL จะทำให้ AVG(grade_point) เพี้ยนทันที เพราะ 0 ถูกนับเข้าไปหาร
ขณะที่ NULL จะถูกข้าม (ดูบทที่ 7 เรื่อง aggregate กับ NULL)
2.5Schema Diagram
Schema diagram คือภาพแสดง schema ของฐานข้อมูล ที่บอกว่ามี relation อะไรบ้าง แต่ละ relation มี attribute อะไร อะไรเป็น primary key และ foreign key เชื่อมไปที่ไหน
สีเหลือง ขีดเส้นใต้ = primary key ·
สีน้ำเงิน = foreign key · ลูกศรชี้ไปยัง relation ที่ถูกอ้างถึง
คอลัมน์หนึ่งเป็นได้ทั้ง PK และ FK พร้อมกัน — เช่น section.course_id เป็นส่วนหนึ่งของ PK ของ section
และเป็น FK ชี้ไป course ด้วย · เช่นเดียวกับ 4 คอลัมน์แรกของ takes ที่เป็นทั้ง PK และ FK ชี้ไป section
2.6Relational Query Language
Relational query language คือภาษาระดับสูงที่ใช้โต้ตอบกับฐานข้อมูลเชิงสัมพันธ์ ให้เรานิยาม จัดการ และดึงข้อมูลจาก relation ได้
- SQL — ภาษาที่ใช้แพร่หลายที่สุด · QUEL — อีกตัวที่พบน้อยกว่า
- Declarative — บอกว่าต้องการข้อมูลอะไร ไม่ใช่ต้องไปเอามายังไง
- ตั้งอยู่บน relational algebra และ relational calculus
- ครอบคลุมทั้ง data definition, manipulation และ control
2.7สรุปบทนี้
| คำ | นิยามที่ต้องเขียนตอบได้ |
|---|---|
| Schema ของ relation | การออกแบบเชิงตรรกะของ relation |
| Instance ของ relation | เนื้อหาของ relation ณ จุดเวลาหนึ่ง |
| Superkey | เซตของ attribute ตั้งแต่หนึ่งตัวขึ้นไป ที่รับประกันว่าระบุ tuple ได้เฉพาะตัว |
| Candidate key | superkey ที่ minimal — เป็น superkey แต่ไม่มีสับเซตใดของมันเป็น superkey |
| Primary key | candidate key ตัวหนึ่งที่ถูกเลือกมาใช้เป็นหลัก |
| Foreign key | เซตของ attribute ใน referencing relation ที่ค่าของมันต้องปรากฏเป็นค่า primary key ของ tuple ใด tuple หนึ่งใน referenced relation |
| Schema diagram | ภาพแสดง relation ทั้งหมด attribute ของแต่ละตัว พร้อม PK และ FK |
Student(student_id, national_id, email, full_name) โดย student_id, national_id และ email ล้วนไม่ซ้ำข้อใดเป็น superkey แต่ไม่ใช่ candidate key
{email} และ {national_id} ต่างก็ minimal → เป็น candidate key · {full_name} ชื่อซ้ำได้ → ไม่ใช่ superkey ด้วยซ้ำ