Quan hệ "nhiều-nhiều" là khi một bản ghi trong một bảng có thể liên kết với nhiều bản ghi ở bảng khác, và ngược lại. Ví dụ: - Một sinh viên (từ bảng students) có thể đăng ký nhiều khóa học (từ bảng courses). - Một khóa học có thể có nhiều sinh viên tham gia.
Vấn đề là lưu trữ trực tiếp kiểu quan hệ này rất bất tiện. Vì vậy, bảng trung gian sẽ giúp ta lưu các liên kết này.
Ví dụ thực tế
Giả sử bạn tạo một bảng sinh viên và một bảng khóa học. Nếu bạn cố thêm vào mỗi bảng một cột để lưu tất cả dữ liệu liên quan, sẽ rất rối:
- Trong bảng
studentsbạn sẽ phải lưu danh sách tất cả các khóa học mà sinh viên đăng ký. Nhưng lưu danh sách kiểu gì? Dùng dấu phẩy? Dùng mảng? Rất khó để truy vấn. - Trong bảng
coursesbạn sẽ phải lưu danh sách sinh viên, và điều này thực sự đau đầu luôn.
Vì vậy, giải pháp đúng là tạo thêm một bảng thứ ba để lưu các liên kết giữa sinh viên và khóa học.
Bảng trung gian: cứu tinh của chúng ta!
Bảng trung gian (còn gọi là bảng liên kết) giải quyết mọi vấn đề. Nó chứa hai khóa ngoại:
- Khóa ngoại trỏ đến bảng
students. - Khóa ngoại trỏ đến bảng
courses.
Mỗi bản ghi trong bảng này tạo ra một liên kết giữa một sinh viên cụ thể và một khóa học cụ thể.
Tạo bảng cho quan hệ "nhiều-nhiều"
Cùng thực hành nhé! Đây là cách tạo bảng để liên kết sinh viên và khóa học:
Bước 1: Tạo bảng students
Đây là bảng sinh viên của chúng ta. Ở đây ta lưu id duy nhất của sinh viên và tên của họ.
CREATE TABLE students (
student_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
student_id— id duy nhất của sinh viên (tự tăng, cảm ơnSERIAL!).name— tên sinh viên.
Bước 2: Tạo bảng courses
Bây giờ tạo bảng cho khóa học. Ở đây ta lưu id duy nhất của khóa học và tên của nó.
CREATE TABLE courses (
course_id SERIAL PRIMARY KEY,
title TEXT NOT NULL
);
course_id— id duy nhất của khóa học.title— tên khóa học.
Bước 3: Tạo bảng trung gian enrollments
Bây giờ tạo bảng liên kết thần thánh của chúng ta. Nó có hai cột, mỗi cột là một khóa ngoại trỏ đến các bảng tương ứng.
CREATE TABLE enrollments (
student_id INT REFERENCES students(student_id),
course_id INT REFERENCES courses(course_id),
PRIMARY KEY (student_id, course_id)
);
Phân tích cấu trúc:
student_id— khóa ngoại trỏ đếnstudent_idcủa bảngstudents.course_id— khóa ngoại trỏ đếncourse_idcủa bảngcourses.PRIMARY KEY (student_id, course_id)— khóa chính là sự kết hợp của hai khóa ngoại. Điều này đảm bảo mỗi liên kết là duy nhất.
Chèn dữ liệu
Thêm chút dữ liệu để test xem mọi thứ hoạt động thế nào nhé.
Bước 1: Thêm sinh viên
INSERT INTO students (name) VALUES
('Alice'),
('Bob'),
('Charlie');
Kết quả:
| student_id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
Bước 2: Thêm khóa học
INSERT INTO courses (title) VALUES
('Mathematics'),
('History'),
('Biology');
Kết quả:
| course_id | title |
|---|---|
| 1 | Mathematics |
| 2 | History |
| 3 | Biology |
Bước 3: Thêm bản ghi vào enrollments
Bây giờ đăng ký sinh viên vào các khóa học. Ví dụ:
Aliceđăng kýMathematicsvàHistory.Bobchỉ đăng kýBiology.Charlieđăng ký cả ba khóa học.
INSERT INTO enrollments (student_id, course_id) VALUES
(1, 1), -- Alice đăng ký Mathematics
(1, 2), -- Alice đăng ký History
(2, 3), -- Bob đăng ký Biology
(3, 1), -- Charlie đăng ký Mathematics
(3, 2), -- Charlie đăng ký History
(3, 3); -- Charlie đăng ký Biology
Kết quả:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
Truy vấn cho quan hệ "nhiều-nhiều"
Bây giờ có dữ liệu rồi, cùng tận dụng nó nhé!
Làm sao tìm tất cả các khóa học mà một sinh viên đã đăng ký?
Ví dụ, để biết Alice (ID = 1) đã đăng ký những khóa nào, chạy truy vấn này:
SELECT c.title
FROM courses c
JOIN enrollments e ON c.course_id = e.course_id
WHERE e.student_id = 1;
Kết quả:
| title |
|---|
| Mathematics |
| History |
Làm sao tìm tất cả sinh viên đã đăng ký một khóa học cụ thể?
Giả sử muốn biết ai đã đăng ký khóa Mathematics (ID = 1):
SELECT s.name
FROM students s
JOIN enrollments e ON s.student_id = e.student_id
WHERE e.course_id = 1;
Kết quả:
| name |
|---|
| Alice |
| Charlie |
Làm sao tìm sinh viên và các khóa học của họ?
Để xem tổng thể ai đăng ký gì, chạy truy vấn này:
SELECT s.name AS student, c.title AS course
FROM students s
JOIN enrollments e ON s.student_id = e.student_id
JOIN courses c ON e.course_id = c.course_id;
Kết quả:
| student | course |
|---|---|
| Alice | Mathematics |
| Alice | History |
| Bob | Biology |
| Charlie | Mathematics |
| Charlie | History |
| Charlie | Biology |
Bảng enrollments làm cho schema của ta cực kỳ linh hoạt — có thể thêm/xóa liên kết giữa sinh viên và khóa học thoải mái mà không ảnh hưởng đến các bảng chính. Nhờ JOIN, việc tìm kiếm dữ liệu rất dễ, ví dụ ai đăng ký khóa nào. Khóa ngoại cũng tự động đảm bảo không có lỗi — ví dụ không thể đăng ký sinh viên vào một khóa học không tồn tại.
Lỗi thường gặp với quan hệ "nhiều-nhiều"
Thiếu ràng buộc duy nhất: Nếu không đặt PRIMARY KEY, có thể vô tình thêm trùng một liên kết nhiều lần.
Vi phạm toàn vẹn dữ liệu: Thử chèn bản ghi với student_id hoặc course_id không tồn tại sẽ bị lỗi.
Xóa dữ liệu không đúng thứ tự: Nếu xóa khóa học khỏi courses trước, các bản ghi trong enrollments sẽ thành "mồ côi". Để tránh điều này, hãy dùng ON DELETE CASCADE trong định nghĩa khóa ngoại.
GO TO FULL VERSION