Hôm nay tụi mình sẽ bắt đầu tìm hiểu cách foreign key giúp tụi mình kiểm soát tính toàn vẹn dữ liệu và tránh các vấn đề phổ biến liên quan đến dữ liệu không đồng nhất hoặc sai lệch.
Trước tiên, hãy làm rõ tụi mình đang nói về "tính toàn vẹn dữ liệu" là gì. Hãy tưởng tượng bạn có một bảng đơn hàng (orders) và một bảng khách hàng (customers). Nếu một đơn hàng có khách mà khách đó không tồn tại trong bảng khách hàng thì đó là vi phạm tính toàn vẹn. Quan trọng là tất cả dữ liệu trong các bảng liên kết phải hợp lý và đồng nhất với nhau.
Tính toàn vẹn dữ liệu có nghĩa là:
- Không có "liên kết rỗng": nếu tụi mình tham chiếu tới cái gì đó trong bảng khác thì cái đó luôn luôn phải tồn tại.
- Chống lỗi khi sửa đổi: nếu xóa khỏi bảng một giá trị mà các bản ghi khác đang tham chiếu tới, database phải cảnh báo hoặc xử lý tình huống này cho tụi mình.
Chính vì vậy mà trong PostgreSQL tụi mình dùng foreign key.
Foreign key đảm bảo tính toàn vẹn dữ liệu như thế nào?
Khi tạo foreign key trong bảng, PostgreSQL sẽ tự động kiểm tra:
- Dữ liệu có tồn tại trong bảng cha không. Trước khi chèn hoặc cập nhật bản ghi, PostgreSQL kiểm tra xem foreign key chỉ tới có tồn tại trong bảng liên kết không.
- Xóa hoặc thay đổi dữ liệu. Trước khi xóa hoặc cập nhật bản ghi trong bảng cha, PostgreSQL kiểm tra xem có bản ghi nào trong bảng con đang tham chiếu tới nó không.
Foreign key giống như "bảo vệ" vậy. Nó không cho phép dữ liệu sai lọt vào và đảm bảo các bảng chỉ tương tác theo đúng luật đã đặt ra.
Ví dụ: Tính toàn vẹn dữ liệu trong bảng sinh viên và khóa học
Giả sử tụi mình có hai bảng — students và courses. Mỗi sinh viên có thể đăng ký nhiều khóa học. Để thể hiện mối liên hệ này, tụi mình dùng bảng enrollments. Hãy xem trường hợp ai đó cố gắng đăng ký sinh viên vào một khóa học không tồn tại.
Bước 1. Tạo ba bảng liên kết:
CREATE TABLE students (
student_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE courses (
course_id SERIAL PRIMARY KEY,
title TEXT NOT NULL
);
CREATE TABLE enrollments (
enrollment_id SERIAL PRIMARY KEY,
student_id INT REFERENCES students(student_id),
course_id INT REFERENCES courses(course_id)
);
Ở đây:
- Trong bảng
enrollmentstụi mình chỉ rõ foreign keystudent_idvàcourse_idtham chiếu tới primary key của bảngstudentsvàcourses.
Những kiểm tra tính toàn vẹn dữ liệu thường gặp
- Kiểm tra khi chèn dữ liệu
Nếu tụi mình thử chèn vào bảng enrollments một bản ghi với student_id hoặc course_id không tồn tại, sẽ bị lỗi liền.
Ví dụ:
INSERT INTO enrollments (student_id, course_id)
VALUES (999, 1); -- Lỗi! Sinh viên với ID 999 không tồn tại.
Thông báo lỗi:
ERROR: insert or update on table "enrollments" violates foreign key constraint "enrollments_student_id_fkey"
DETAIL: Key (student_id)=(999) is not present in table "students".
- Kiểm tra khi xóa dữ liệu
Thử xóa bản ghi trong bảng cha mà đang có bản ghi khác tham chiếu tới nó xem sao.
Ví dụ:
INSERT INTO students (name) VALUES ('Alice');
INSERT INTO courses (title) VALUES ('Mathematics');
INSERT INTO enrollments (student_id, course_id)
VALUES (1, 1); -- Chèn thành công
DELETE FROM students WHERE student_id = 1; -- Lỗi, vì sinh viên này vẫn còn đăng ký khóa học!
Thông báo lỗi:
ERROR: update or delete on table "students" violates foreign key constraint "enrollments_student_id_fkey" on table "enrollments"
DETAIL: Key (student_id)=(1) is still referenced from table "enrollments".
Để xóa bản ghi đúng cách trong trường hợp này, tụi mình dùng các chiến lược CASCADE, SET NULL hoặc RESTRICT đã nói ở bài trước.
Ví dụ dùng foreign key để kiểm tra tính toàn vẹn
Ví dụ 1: Tự động bảo vệ khỏi dữ liệu sai
Nhờ foreign key, PostgreSQL tự động ngăn không cho chèn dữ liệu "không tồn tại":
-- Thử thêm sinh viên không tồn tại vào khóa học:
INSERT INTO enrollments (student_id, course_id)
VALUES (42, 1); -- Lỗi! Sinh viên với ID 42 không tồn tại.
Điều này đảm bảo sinh viên không thể đăng ký khóa học nếu chưa có trong bảng students.
Ví dụ 2: Xóa dữ liệu với ON DELETE CASCADE
Nếu foreign key được cấu hình với ON DELETE CASCADE, khi xóa bản ghi trong bảng cha thì dữ liệu liên quan trong bảng con cũng sẽ bị xóa theo.
ALTER TABLE enrollments DROP CONSTRAINT enrollments_student_id_fkey; -- Xóa foreign key cũ
ALTER TABLE enrollments
ADD CONSTRAINT enrollments_student_id_fkey FOREIGN KEY (student_id)
REFERENCES students(student_id) ON DELETE CASCADE;
DELETE FROM students WHERE student_id = 1; -- Bây giờ bản ghi trong enrollments cũng bị xóa luôn
Ví dụ 3: Xử lý thay đổi với ON UPDATE
Nếu foreign key được cấu hình với ON UPDATE CASCADE, khi thay đổi giá trị trong bảng cha, PostgreSQL sẽ tự động cập nhật dữ liệu trong bảng con.
-- Cấu hình foreign key để khi thay đổi khóa cha thì bảng con cũng tự động cập nhật:
ALTER TABLE enrollments DROP CONSTRAINT enrollments_student_id_fkey;
ALTER TABLE enrollments
ADD CONSTRAINT enrollments_student_id_fkey FOREIGN KEY (student_id)
REFERENCES students(student_id) ON UPDATE CASCADE;
-- Thay đổi student_id:
UPDATE students SET student_id = 10 WHERE student_id = 1;
-- Bây giờ trong bảng enrollments student_id cũng sẽ thành 10.
Kiểm thử tính toàn vẹn dữ liệu
Luôn luôn nên kiểm thử xem cấu hình foreign key hoạt động thế nào trong các tình huống khác nhau:
- Thử chèn dữ liệu với
student_idhoặccourse_idsai. - Xóa dữ liệu trong
studentsvà xem bảngenrollmentsphản ứng ra sao. - Thay đổi dữ liệu trong bảng
studentsvà kiểm tra bản ghi liên quan có được cập nhật không.
Lưu ý khi làm việc với foreign key
Đôi khi sẽ gặp những tình huống dễ gây bối rối:
- Thiếu index. Nếu bảng cha (
studentschẳng hạn) không có cột được index mà bị tham chiếu, PostgreSQL có thể xử lý chậm hơn. Vì vậy, primary key trong bảng cha luôn nên là index. - Liên kết vòng. Nếu hai bảng tham chiếu lẫn nhau, có thể gây khó khăn khi chèn dữ liệu. Trường hợp này cần thiết kế cẩn thận hơn.
- Xóa toàn bộ bản ghi. Nếu cần xóa hết bản ghi với cascade, phải chú ý dữ liệu trong bảng con để tránh hành vi bất ngờ.
Để tránh các vấn đề này, hãy thiết kế bảng hợp lý và kiểm thử luật liên kết trước khi áp dụng vào database thật nhé.
GO TO FULL VERSION