Hôm nay tụi mình sẽ đào sâu vào một chủ đề siêu quan trọng: mô hình hóa quan hệ giữa các bảng. Chuẩn hóa không chỉ là về dữ liệu nguyên tử và loại bỏ dư thừa, mà còn là về việc tạo ra các quan hệ đúng đắn giữa các bảng nữa.
Nếu database là một hệ thống lưu trữ thông tin có tổ chức, thì các quan hệ giữa các bảng chính là những cây cầu logic, cho thấy dữ liệu tương tác với nhau như thế nào. Hãy tưởng tượng một thư viện, nơi thông tin về sách được lưu riêng biệt với thông tin về tác giả, nhưng mỗi cuốn sách lại "biết" tác giả của mình thông qua một quan hệ đặc biệt. Hoặc lấy ví dụ shop online: dữ liệu về sản phẩm tồn tại độc lập với thông tin khách hàng, nhưng khi ai đó đặt hàng, hệ thống sẽ liên kết khách hàng cụ thể với các sản phẩm cụ thể thông qua bảng đơn hàng.
Ở phòng khám, bệnh nhân được liên kết với hồ sơ y tế của mình, bác sĩ thì gắn với lịch khám, còn thuốc thì gắn với đơn kê. Những quan hệ này giúp hệ thống hiểu thông tin nào liên quan đến cái gì, mà không cần lặp lại dữ liệu một cách không cần thiết.
Các loại quan hệ cơ bản này hoạt động giống như các mối quan hệ ngoài đời: hộ chiếu chỉ thuộc về một người (một-một), một giảng viên có thể dạy nhiều khóa học (một-nhiều), còn sinh viên thì có thể đăng ký nhiều môn, mỗi môn lại có nhiều sinh viên học (nhiều-nhiều).
Một-một (1:1)
Đây là quan hệ mà một bản ghi trong bảng "A" chỉ tương ứng đúng một bản ghi trong bảng "B". Ví dụ, tụi mình có bảng "Nhân viên" và "Thông tin hộ chiếu". Một nhân viên chỉ có một hộ chiếu, và mỗi hộ chiếu chỉ thuộc về một nhân viên duy nhất.
Ví dụ:
Nhân viên
| id | tên | chức vụ |
|---|---|---|
| 1 | Otto Lin | quản lý |
Thông tin hộ chiếu
| id | nhanvien_id | số hộ chiếu |
|---|---|---|
| 1 | 1 | 123456789 |
Ở đây quan hệ được thực hiện qua foreign key nhanvien_id, trỏ tới id trong bảng "Nhân viên".
Một-nhiều (1:N)
Đây là loại quan hệ phổ biến nhất. Ở đây mỗi bản ghi trong bảng "A" có thể liên kết với nhiều bản ghi trong bảng "B", nhưng mỗi bản ghi trong "B" chỉ liên kết với một bản ghi trong "A". Ví dụ, tụi mình có bảng "Giảng viên" và "Khóa học". Một giảng viên có thể dạy nhiều khóa học.
Ví dụ:
Giảng viên
| id | tên |
|---|---|
| 1 | Anna Song |
| 2 | Alek Min |
Khóa học
| id | tên khóa học | giangvien_id |
|---|---|---|
| 1 | Cơ bản SQL | 1 |
| 2 | Quản trị DB | 1 |
| 3 | Lập trình Python | 2 |
Quan hệ được tạo qua foreign key giangvien_id trong bảng "Khóa học".
Nhiều-nhiều (M:N)
Khi có nhiều nhiều thứ, thì vui nhưng cũng phức tạp. Ở đây mỗi bản ghi trong bảng "A" có thể liên kết với nhiều bản ghi trong bảng "B", và ngược lại. Ví dụ, sinh viên có thể đăng ký nhiều khóa học, mỗi khóa học lại có nhiều sinh viên học.
Ví dụ:
Sinh viên
| id | tên |
|---|---|
| 1 | Otto Lin |
| 2 | Maria Chi |
Khóa học
| id | tên khóa học |
|---|---|
| 1 | Cơ bản SQL |
| 2 | Quản trị DB |
Để liên kết, tụi mình cần một bảng trung gian, lưu các cặp sinh viên và khóa học:
Ghi danh
| id | sinhvien_id | khoahoc_id |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
Mô hình hóa quan hệ bằng foreign key
Foreign key là một cột (hoặc tập cột) trỏ tới cột primary key ở bảng khác. Đây là nền tảng để xây dựng quan hệ giữa các bảng.
Ví dụ về foreign key:
CREATE TABLE Khoá_học (
id SERIAL PRIMARY KEY,
tên VARCHAR(255)
);
CREATE TABLE Ghi_danh (
id SERIAL PRIMARY KEY,
sinhvien_id INT,
khoahoc_id INT,
FOREIGN KEY (khoahoc_id) REFERENCES Khoá_học(id)
);
Làm sao tránh lỗi khi thiết kế foreign key? Đầu tiên, phải đảm bảo kiểu dữ liệu giữa cột foreign key và primary key giống nhau — nếu không database sẽ không cho tạo quan hệ đâu. Ngoài ra, nên nghĩ trước xem khi xóa bản ghi thì chuyện gì sẽ xảy ra với các bản ghi liên quan. Ví dụ, nếu bạn xóa dòng ở bảng cha, còn các bản ghi con thì sao? Một cách phổ biến là dùng ON DELETE CASCADE, để dữ liệu liên quan tự động bị xóa cùng bản ghi chính. Như vậy sẽ gọn gàng và không bị "link mồ côi".
Triển khai quan hệ "Nhiều-nhiều"
Lấy ví dụ: tụi mình có sinh viên và khóa học. Một sinh viên có thể ghi danh nhiều khóa học, một khóa học cũng có nhiều sinh viên. Để làm quan hệ M:N, tạo ba bảng: Sinh viên, Khóa học và Ghi danh.
CREATE TABLE Sinh_vien (
id SERIAL PRIMARY KEY,
tên VARCHAR(255)
);
CREATE TABLE Khoá_học (
id SERIAL PRIMARY KEY,
tên VARCHAR(255)
);
CREATE TABLE Ghi_danh (
id SERIAL PRIMARY KEY,
sinhvien_id INT,
khoahoc_id INT,
FOREIGN KEY (sinhvien_id) REFERENCES Sinh_vien(id),
FOREIGN KEY (khoahoc_id) REFERENCES Khoá_học(id)
);
Bây giờ tụi mình có thể thêm dữ liệu vào bảng Ghi danh để liên kết sinh viên và khóa học.
Bài tập thực hành
Tạo cấu trúc database cho hệ thống quản lý khóa học. Bạn cần có các bảng Sinh viên, Khóa học và Ghi danh. Triển khai đầy đủ các quan hệ giữa các bảng. Sau đó nhập thử dữ liệu về sinh viên, khóa học và ghi danh. Cùng xem cách làm nhé.
- Tạo bảng:
CREATE TABLE Sinh_vien (
id SERIAL PRIMARY KEY,
tên VARCHAR(255)
);
CREATE TABLE Khoá_học (
id SERIAL PRIMARY KEY,
tên VARCHAR(255)
);
CREATE TABLE Ghi_danh (
id SERIAL PRIMARY KEY,
sinhvien_id INT,
khoahoc_id INT,
FOREIGN KEY (sinhvien_id) REFERENCES Sinh_vien(id),
FOREIGN KEY (khoahoc_id) REFERENCES Khoá_học(id)
);
- Chèn dữ liệu:
INSERT INTO Sinh_vien (tên) VALUES ('Otto Lin'), ('Maria Chi');
INSERT INTO Khoá_học (tên) VALUES ('Cơ bản SQL'), ('Quản trị DB');
INSERT INTO Ghi_danh (sinhvien_id, khoahoc_id) VALUES (1, 1), (1, 2), (2, 1);
- Kiểm tra dữ liệu:
SELECT
Sinh_vien.tên AS sinhvien,
Khoá_học.tên AS khoahoc
FROM Ghi_danh
JOIN Sinh_vien ON Ghi_danh.sinhvien_id = Sinh_vien.id
JOIN Khoá_học ON Ghi_danh.khoahoc_id = Khoá_học.id;
Kết quả:
| sinhvien | khoahoc |
|---|---|
| Otto Lin | Cơ bản SQL |
| Otto Lin | Quản trị DB |
| Maria Chi | Cơ bản SQL |
Khó khăn và lưu ý khi mô hình hóa quan hệ
Khi bạn mô hình hóa quan hệ giữa các bảng, có thể gặp các vấn đề như:
- Lỗi khi xóa dữ liệu (ví dụ, có bản ghi ở bảng con phụ thuộc vào bản ghi ở bảng cha).
- Hiệu suất query khi dữ liệu lớn. Quan hệ M:N đặc biệt "ngốn", vì cần join thêm bảng trung gian.
Để giải quyết, bạn nên:
- Dùng index cho foreign key
- Thiết kế cấu trúc database hợp lý.
- Cân bằng giữa chuẩn hóa và hiệu suất.
Tụi mình vừa xem qua mô hình hóa quan hệ giữa các bảng ở mức cơ bản và thực hành luôn với hệ thống quản lý khóa học. Thật ra cũng muốn làm ví dụ lớn hơn, nhưng nghĩ mãi không ra cách cho hay. Ví dụ lớn thì lại phức tạp và chán. Làm vậy cũng không giúp ích nhiều. Để cuối khóa mình sẽ thử quay lại chủ đề này nhé.
GO TO FULL VERSION