Chuyển đến nội dung chính

Ràng Buộc Toàn Vẹn trong SQL: Hướng Dẫn kèm Ví Dụ

Ràng buộc toàn vẹn trong SQL là các quy tắc áp dụng trên bảng cơ sở dữ liệu để duy trì tính chính xác, nhất quán và hợp lệ của dữ liệu, như đảm bảo khóa chính là duy nhất và mối quan hệ khóa ngoại hợp lệ.
· 15 phút đọc

Khám phá cùng AI

Hãy hình dung một cơ sở dữ liệu bệnh viện nơi thông tin dị ứng của bệnh nhân không được để trống, hoặc một hệ thống tài chính nơi số tiền giao dịch phải là số dương. Trong những tình huống này và vô số tình huống khác, chúng ta dựa vào các ràng buộc toàn vẹn để đảm bảo dữ liệu luôn chính xác, nhất quán và đáng tin cậy. 

Trong SQL, ràng buộc toàn vẹn là các quy tắc chúng ta áp dụng lên các bảng cơ sở dữ liệu để duy trì chất lượng dữ liệu. Chúng giúp ngăn lỗi, thực thi các quy tắc nghiệp vụ và đảm bảo dữ liệu phản ánh đúng các thực thể và mối quan hệ ngoài đời thực mà nó biểu diễn.

Trong bài viết này, chúng ta sẽ đi sâu vào các loại ràng buộc toàn vẹn thiết yếu trong SQL, kèm giải thích rõ ràng và ví dụ thực tiễn minh họa cách dùng trong cơ sở dữ liệu PostgreSQL. Dù sử dụng cú pháp PostgreSQL, các khái niệm này có thể áp dụng dễ dàng cho các biến thể SQL khác.

Nếu bạn muốn học thêm về SQL, hãy xem danh sách các khóa học SQL này.

Ràng Buộc Toàn Vẹn trong SQL là gì?

Hãy xét một tình huống có bảng lưu thông tin người dùng cho một ứng dụng web. Một số dữ liệu như tuổi người dùng có thể là tùy chọn vì không ảnh hưởng đến việc truy cập ứng dụng. Tuy nhiên, mật khẩu cho mỗi người dùng là bắt buộc để đăng nhập. Để xử lý điều này, chúng ta sẽ áp dụng một ràng buộc toàn vẹn lên cột mật khẩu trong bảng người dùng để đảm bảo mọi bản ghi trong bảng đều có mật khẩu.

Về bản chất, ràng buộc toàn vẹn rất quan trọng để:

  • Ngăn dữ liệu bị thiếu.
  • Đảm bảo mọi dữ liệu tuân theo kiểu và phạm vi giá trị kỳ vọng.
  • Duy trì các liên kết đúng đắn giữa dữ liệu ở các bảng khác nhau.

Trong bài này, chúng ta sẽ khám phá các ràng buộc toàn vẹn thiết yếu sau trong SQL:

  • PRIMARY KEY: Nhận diện duy nhất mỗi bản ghi trong một bảng.
  • NOT NULL: Đảm bảo một cột không thể chứa giá trị NULL.
  • UNIQUE: Đảm bảo tất cả giá trị trong một cột hoặc nhóm cột là duy nhất.
  • DEFAULT: Cung cấp giá trị mặc định cho một cột khi không được chỉ định.
  • CHECK: Đảm bảo mọi giá trị trong một cột thỏa mãn một điều kiện cụ thể.
  • FOREIGN KEY: Thiết lập quan hệ giữa các bảng bằng cách tham chiếu đến khóa chính ở bảng khác.

Bài Toán Nghiên Cứu: Cơ Sở Dữ Liệu Trường Đại Học

Hãy xét một cơ sở dữ liệu quan hệ cho một trường đại học. Cơ sở dữ liệu này có ba bảng: students, courses và enrollments.

Bảng Students

Bảng students lưu thông tin về tất cả sinh viên của trường.

  • student_id: Định danh của sinh viên
  • first_name: Tên của sinh viên.
  • last_name: Họ của sinh viên.
  • email: Địa chỉ email của sinh viên.
  • major: Chuyên ngành của sinh viên.
  • enrollment_year: Năm sinh viên nhập học.

Bảng Courses

Bảng courses lưu thông tin về các học phần có tại trường.

  • course_id: Định danh học phần.
  • course_name: Tên học phần.
  • department: Khoa/phòng ban phụ trách học phần.

Bảng Enrollments

Bảng enrollments lưu thông tin về sinh viên nào đăng ký học phần nào.

  • student_id: Định danh của sinh viên đăng ký học phần.
  • course_id: Định danh của học phần.
  • year: Năm đăng ký.
  • grade: Điểm của sinh viên ở học phần này.
  • is_passing_grade: Boolean cho biết điểm có đạt yêu cầu hay không.

Xuyên suốt bài viết, chúng ta sẽ dùng cơ sở dữ liệu ví dụ này và trình bày nhiều cách để đảm bảo toàn vẹn dữ liệu. Chúng ta sử dụng cú pháp PostgreSQL trong truy vấn, tuy nhiên các khái niệm rất dễ chuyển sang những biến thể SQL khác.

Ràng Buộc PRIMARY KEY

Trường đại học muốn nhận diện duy nhất từng sinh viên. Sử dụng tên và họ cho mục đích này không phù hợp do có thể trùng tên giữa các sinh viên. Tương tự, dựa vào địa chỉ email cũng không lý tưởng vì sinh viên có thể thay đổi email.

Giải pháp phổ biến để đảm bảo nhận diện duy nhất là gán cho mỗi sinh viên một mã định danh duy nhất, lưu trong cột student_id. Chúng ta có thể áp dụng ràng buộc PRIMARY KEY lên cột student_id để bảo đảm mỗi sinh viên có một định danh duy nhất.

Ràng buộc này được định nghĩa trong lệnh CREATE TABLE, ngay sau phần khai báo kiểu dữ liệu của cột:

CREATE TABLE students (
    student_id INT PRIMARY KEY,
    first_name TEXT,
    last_name TEXT,
    email TEXT,
    major TEXT,
    enrollment_year INT
);

Truy vấn trên tạo bảng sinh viên với sáu cột như đã nêu.

Ràng buộc PRIMARY KEY đảm bảo rằng:

  1. Mỗi sinh viên có một student_id.
  2. Mỗi student_id là duy nhất.

Ràng buộc PRIMARY KEY trên nhiều cột

Trong một số tình huống, chúng ta cần sử dụng nhiều cột để nhận diện duy nhất mỗi hàng. Chẳng hạn bảng enrollments. Một sinh viên có thể đăng ký nhiều học phần, dẫn đến nhiều hàng có cùng student_id. Tương tự, một học phần có thể có nhiều sinh viên, dẫn đến nhiều hàng có cùng course_id.

Vì không có trường đơn lẻ nào nhận diện duy nhất một hàng, mỗi bản ghi đăng ký được xác định bằng kết hợp student_id, course_id và year.

Khi có nhiều cột, ràng buộc PRIMARY KEY được chỉ định ở cuối lệnh CREATE TABLE.

CREATE TABLE enrollments (
    student_id INT,
    course_id INT,
    year INT,
    grade INT,
    is_passing_grade BOOLEAN,
    PRIMARY KEY (student_id, course_id, year)
);

Ràng buộc toàn vẹn sau khi tạo bảng

Có hai cách để thêm ràng buộc toàn vẹn. Chúng ta vừa học cách làm khi tạo bảng.

Giả sử bảng đã tồn tại nhưng bạn quên chỉ định ràng buộc PRIMARY KEY. Bạn cũng có thể định nghĩa sau khi tạo bảng bằng lệnh ALTER TABLE như sau:

ALTER TABLE enrollments
ADD CONSTRAINT enroll_pk 
PRIMARY KEY (student_id, course_id, year);

Trong truy vấn ALTER TABLE, chúng ta đặt tên ràng buộc là enroll_pk (viết tắt của primary key của bảng enrollments). Tên này có thể là bất kỳ định danh nào bạn chọn, nhưng nên chọn tên truyền tải ngắn gọn mục đích của ràng buộc.

Việc đặt tên cho ràng buộc toàn vẹn là thực hành tốt vì mang lại vài lợi ích:

  • Giúp tham chiếu dễ dàng hơn, đặc biệt khi bạn cần sửa hoặc xóa ràng buộc sau này.
  • Hỗ trợ quản lý và tổ chức ràng buộc, nhất là trong các cơ sở dữ liệu có số lượng ràng buộc lớn.

Ràng Buộc NOT NULL

Trường đại học muốn đảm bảo tên và email của mọi sinh viên đều được ghi nhận trong cơ sở dữ liệu. Họ không muốn nhân viên vô tình quên nhập bất kỳ trường nào trong số đó.

Để xử lý, chúng ta có thể dùng các ràng buộc NOT NULL trên ba cột này khi tạo bảng:

CREATE TABLE students (
    student_id INT,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    email TEXT NOT NULL,
    major TEXT,
    enrollment_year INT
);

Truy vấn trên sử dụng ràng buộc NOT NULL để đảm bảo các cột first_name, last_name và email không thể có giá trị NULL (không xác định).

Thêm ràng buộc NOT NULL vào một bảng hiện có có thể thực hiện bằng lệnh ALTER TABLE. Cú pháp để thêm ràng buộc NOT NULL như sau:

ALTER TABLE students
ALTER COLUMN first_name SET NOT NULL,
ALTER COLUMN last_name SET NOT NULL,
ALTER COLUMN email SET NOT NULL;

Ràng Buộc UNIQUE

Địa chỉ email vốn dĩ là duy nhất với mỗi cá nhân. Trong những tình huống chúng ta chắc chắn một trường không bao giờ được lặp giá trị, việc thực thi điều này ở cấp cơ sở dữ liệu là thực hành tốt. Điều này giúp ngăn sai sót và đảm bảo toàn vẹn dữ liệu.

Việc thêm ràng buộc này thực hiện tương tự như các ràng buộc khác.

CREATE TABLE students (
    ...
    email TEXT UNIQUE,
    ...
);

Tuy nhiên, trong trường hợp của chúng ta, ta cũng muốn áp dụng ràng buộc NOT NULL. Ta có thể áp dụng nhiều ràng buộc trên một cột bằng cách đặt cách nhau bằng khoảng trắng: 

CREATE TABLE students (
    ...
    email TEXT UNIQUE NOT NULL,
    ...
);

Lưu ý thứ tự không quan trọng, ta cũng có thể dùng NOT NULL UNIQUE.

Thêm ràng buộc UNIQUE vào một bảng hiện có được thực hiện như sau:

ALTER TABLE students 
ADD CONSTRAINT unique_emails UNIQUE (email);

Ràng buộc UNIQUE trên nhiều cột

Giả sử chúng ta muốn đảm bảo tên học phần của mỗi khoa là duy nhất trong bảng courses. Khi đó, hai cột course_name và department kết hợp với nhau phải là duy nhất.

Khi có nhiều cột, ràng buộc được thêm ở cuối lệnh CREATE TABLE:

CREATE TABLE courses (
    course_id INT,
    course_name TEXT,
    department TEXT,
    UNIQUE (course_name, department)
);

Ngoài ra, chúng ta có thể thêm ràng buộc bằng cách sửa bảng hiện có. Khi đó, ta cung cấp một bộ gồm tên các cột:

ALTER TABLE courses 
ADD CONSTRAINT unique_course_name_department 
UNIQUE (course_name, department);

So sánh NOT NULL UNIQUE và PRIMARY KEY 

Chúng ta đã biết ràng buộc PRIMARY KEY vừa đảm bảo tính duy nhất vừa không có giá trị thiếu. Vậy khác biệt giữa:

course_id INT PRIMARY KEY
course_id INT UNIQUE NOT NULL

Sự khác nhau giữa NOT NULL UNIQUE và PRIMARY KEY trên một bảng nằm ở mục đích và cách sử dụng. 

Dù cả hai đều thực thi tính duy nhất và không rỗng trên cột (hoặc các cột), một bảng chỉ có duy nhất một PRIMARY KEY, dùng để nhận diện duy nhất mỗi bản ghi trong bảng.

Mặt khác, tổ hợp ràng buộc NOT NULL UNIQUE có thể áp dụng cho các cột bổ sung để đảm bảo giá trị duy nhất trên mỗi hàng và không có giá trị rỗng, phục vụ việc duy trì toàn vẹn dữ liệu theo các quy tắc nghiệp vụ cụ thể. Một bảng có thể có bất kỳ số lượng ràng buộc NOT NULL UNIQUE nào.

Sự tồn tại của cả hai loại mang lại tính linh hoạt cao hơn trong thiết kế cơ sở dữ liệu, cho phép nhiều cách để thực thi tính duy nhất và toàn vẹn dữ liệu, đồng thời phân biệt giữa định danh chính của bản ghi và các thuộc tính quan trọng, duy nhất khác trong bảng.

Ràng Buộc DEFAULT

Sinh viên có thể cần một thời gian sau khi đăng ký nhập học để chọn chuyên ngành. Trường muốn giá trị của cột chuyên ngành là chuỗi ’Undeclared’ cho các sinh viên chưa chọn chuyên ngành.

Để làm vậy, chúng ta có thể đặt giá trị mặc định cho cột này bằng ràng buộc DEFAULT. Ta có thể sửa bảng students như sau:

ALTER TABLE students
ALTER COLUMN major SET DEFAULT 'Undeclared';

Nếu muốn đặt ràng buộc DEFAULT ngay khi tạo bảng, ta có thể khai báo sau kiểu dữ liệu của cột:

CREATE TABLE students (
    ...
    major TEXT DEFAULT 'Undeclared',
    ...
);

Ràng Buộc CHECK

Tại trường đại học này, thang điểm từ 0 đến 100. Nếu không có ràng buộc nào, cột grade của bảng enrollment sẽ chấp nhận mọi số nguyên. Chúng ta có thể khắc phục bằng ràng buộc CHECK để buộc giá trị nằm trong khoảng 0 đến 100.

ALTER TABLE enrollments
ADD CONSTRAINT grade_range CHECK (grade BETWEEN 0 AND 100);

Nói chung, ràng buộc CHECK cho phép chúng ta xác thực các điều kiện cụ thể mà dữ liệu cần thỏa. Đây là những ràng buộc quan trọng để đảm bảo tính nhất quán và toàn vẹn dữ liệu.

Một ràng buộc CHECK có thể liên quan đến nhiều hơn một cột. Hãy dùng nó để đảm bảo grade và is_passing_grade có giá trị nhất quán. Giả sử một điểm được coi là đạt nếu có giá trị ít nhất 60. Khi đó, chúng ta có thể đảm bảo is_passing_grade là TRUE khi và chỉ khi điểm từ 60 trở lên. Hãy thực hiện ngay trong lúc tạo bảng để minh họa cách khai báo ràng buộc CHECK trong lệnh CREATE TABLE:

CREATE TABLE enrollments (
    ...
    grade INT,
    is_passing_grade BOOLEAN,
    CONSTRAINT grade_check CHECK (grade BETWEEN 0 AND 100),
    CONSTRAINT is_passing_grade CHECK (
        (grade >= 60 AND is_passing_grade = TRUE) OR
        (grade < 60 AND is_passing_grade = FALSE)
    )
);

Có một vấn đề với ràng buộc trên. Khi sinh viên đăng ký một học phần, họ chưa có điểm. Vì vậy, chúng ta nên cho phép giá trị NULL ở cột điểm. Khi làm vậy, ta cũng cần cập nhật ràng buộc điểm đạt để là NULL khi điểm chưa được xác định. Dưới đây là cách cập nhật ràng buộc để tính đến các trường hợp này:

CREATE TABLE enrollments (
    ...    
    grade INT NULL DEFAULT NULL,
    is_passing_grade BOOLEAN NULL DEFAULT NULL,
    CONSTRAINT grade_check CHECK (grade BETWEEN 0 AND 100),
    CONSTRAINT is_passing_grade CHECK (
        (grade IS NULL AND is_passing_grade IS NULL) OR
        (grade >= 60 AND is_passing_grade = TRUE) OR
        (grade < 60 AND is_passing_grade = FALSE)
    ),
    ...
);

Lưu ý rằng ở cả hai cột grade và is_passing_grade chúng ta thêm NULL vào kiểu dữ liệu và làm giá trị mặc định. Mục đích chỉ để tăng tính dễ đọc.

Giờ hãy xem các điều kiện nào có thể áp dụng bằng ràng buộc CHECK.

Điều kiện khoảng

Chúng ta có thể đảm bảo các giá trị trong một cột nằm trong một khoảng cụ thể.

ALTER TABLE table_name
ADD CONSTRAINT constraint_name 
CHECK (column_name BETWEEN min_value AND max_value);

Điều kiện danh sách

Chúng ta có thể xác thực rằng giá trị của một cột khớp với một trong các giá trị trong danh sách chỉ định.

ALTER TABLE table_name
ADD CONSTRAINT constraint_name 
CHECK (column_name IN ('Value1', 'Value2', 'Value3'));

Điều kiện so sánh

Chúng ta có thể so sánh các giá trị trong một cột để đảm bảo chúng thỏa điều kiện nhất định (lớn hơn, nhỏ hơn, v.v.).

ALTER TABLE table_name
ADD CONSTRAINT constraint_name 
CHECK (column_name > some_value);

Điều kiện khớp mẫu

Chúng ta có thể dùng khớp mẫu (ví dụ với LIKE hoặc SIMILAR TO) để xác thực dữ liệu văn bản.

ALTER TABLE table_name
ADD CONSTRAINT constraint_name 
CHECK (column_name LIKE 'pattern');

Điều kiện logic

Chúng ta có thể cho phép nhiều điều kiện bằng các toán tử logic (AND, OR, NOT).

ALTER TABLE table_name
ADD CONSTRAINT constraint_name 
CHECK (condition1 AND condition2 OR condition3);

Điều kiện tổng hợp

Áp dụng một điều kiện trên nhiều cột.

ALTER TABLE table_name
ADD CONSTRAINT constraint_name 
CHECK (column1 + column2 < some_value);

Ràng Buộc FOREIGN KEY

Ràng buộc khóa ngoại được dùng để liên kết các cột của hai bảng với nhau, đảm bảo tính toàn vẹn tham chiếu của dữ liệu. Về bản chất, một khóa ngoại trong bảng này trỏ đến một khóa chính ở bảng khác, cho biết các hàng ở hai bảng này có liên quan. Điều này đảm bảo bạn không thể có một hàng trong bảng với khóa ngoại mà không khớp với bất kỳ hàng nào ở bảng được liên kết có khóa chính.

Trong ví dụ của chúng ta, mỗi bản ghi trong bảng enrollments tham chiếu đến một sinh viên và một học phần thông qua các cột student_id và course_id tương ứng. Nếu không có ràng buộc nào, sẽ không có gì đảm bảo rằng các giá trị định danh này trong bảng enrollments khớp với các bản ghi hiện có trong bảng students và courses.

Đây là cách chúng ta đảm bảo điều đó khi tạo bảng enrollments:

CREATE TABLE enrollments (
    student_id INT,
    course_id INT,
    ...
    FOREIGN KEY (student_id) REFERENCES Students(student_id),
    FOREIGN KEY (course_id) REFERENCES Courses(course_id)
);

Lưu ý để chúng là khóa ngoại, chúng phải là khóa chính trong các bảng students và courses tương ứng.

Tương tự các ràng buộc khác, chúng ta cũng có thể khai báo sau khi tạo bảng bằng lệnh ALTER TABLE:

ALTER TABLE enrollments
ADD CONSTRAINT fk_student_id
FOREIGN KEY (student_id)
REFERENCES students(student_id);

ALTER TABLE enrollments
ADD CONSTRAINT fk_course_id
FOREIGN KEY (course_id)
REFERENCES courses(course_id);

Tổng Hợp

Xuyên suốt bài viết, chúng ta đã trình bày nhiều ràng buộc toàn vẹn và cách dùng để cải thiện cơ sở dữ liệu của một trường đại học. Dưới đây là phiên bản cuối cùng của lệnh CREATE TABLE, kết hợp mọi thứ chúng ta đã học.

Trước hết, chúng ta sẽ định nghĩa bảng students:

CREATE TABLE students (
    student_id INT PRIMARY KEY,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    major TEXT DEFAULT 'Undeclared',
    enrollment_year INT,
    CONSTRAINT year_check CHECK (enrollment_year >= 1900),
    CHECK (major IN (
        'Undeclared',
        'Computer Science',
        'Mathematics',
        'Biology',
        'Physics',
        'Chemistry',
        'Biochemistry'
    ))
);

Tiếp theo, hãy định nghĩa bảng courses:

CREATE TABLE courses (
    course_id INT PRIMARY KEY,
    course_name TEXT NOT NULL,
    department TEXT NOT NULL,
    UNIQUE (course_name, department),
    CHECK (department IN (
        'Physics & Mathematics',
        'Sciences'
    ))
);

Cuối cùng, chúng ta sẽ định nghĩa bảng enrollments, thiết lập các mối quan hệ giữa sinh viên và học phần:

CREATE TABLE enrollments (
    student_id INT,
    course_id INT,
    year INT CHECK (year >= 1900),
    grade INT NULL DEFAULT NULL,
    is_passing_grade BOOLEAN NULL DEFAULT NULL,
    CONSTRAINT grade_check CHECK (grade BETWEEN 0 AND 100),
    CONSTRAINT is_passing_grade CHECK (
        (grade IS NULL AND is_passing_grade IS NULL) OR
        (grade >= 60 AND is_passing_grade = TRUE) OR
        (grade < 60 AND is_passing_grade = FALSE)
    ),
    FOREIGN KEY (student_id) REFERENCES students(student_id),
    FOREIGN KEY (course_id) REFERENCES courses(course_id),
    PRIMARY KEY (student_id, course_id, year)
);

Chúng ta đã thêm một vài ràng buộc trong ví dụ cuối cùng này. Một trường đại học có tập hợp khoa và chuyên ngành đã biết trước. Do đó, tập giá trị mà các cột major và department có thể nhận là hữu hạn và biết trước. Trong những tình huống như vậy, nên dùng ràng buộc CHECK để đảm bảo các cột chỉ nhận giá trị từ tập đã biết đó.

Kết Luận

Trong bài viết này, chúng ta đã khám phá các loại ràng buộc toàn vẹn khác nhau trong SQL và cách triển khai chúng bằng PostgreSQL. Chúng ta đã đề cập đến khóa chính, ràng buộc NOT NULL, ràng buộc UNIQUE, ràng buộc DEFAULT, ràng buộc CHECK và ràng buộc FOREIGN KEY, kèm ví dụ thực tiễn cho từng loại.

Bằng cách hiểu các khái niệm này, chúng ta có thể đảm bảo tính chính xác, nhất quán và độ tin cậy của dữ liệu.

Nếu bạn muốn học thêm về cách tổ chức dữ liệu hiệu quả, hãy xem khóa học về Thiết kế Cơ sở Dữ liệu này.

François Aubry

Tác giả

François Aubry

Kỹ sư full-stack & nhà sáng lập tại CheapGPT. Giảng dạy luôn là niềm đam mê của tôi. Từ những ngày còn là sinh viên, tôi đã háo hức tìm kiếm cơ hội kèm cặp và hỗ trợ các bạn học khác. Niềm đam mê đó đã thôi thúc tôi theo học tiến sĩ, nơi tôi cũng đảm nhiệm vai trò trợ giảng để hỗ trợ con đường học thuật của mình. Trong những năm ấy, tôi tìm thấy sự mãn nguyện lớn lao trong môi trường lớp học truyền thống, xây dựng kết nối và thúc đẩy việc học tập. Tuy nhiên, với sự ra đời của các nền tảng học trực tuyến, tôi nhận ra tiềm năng thay đổi của giáo dục số. Thực tế, tôi đã trực tiếp tham gia phát triển một nền tảng như vậy tại trường đại học của chúng tôi. Tôi tận tâm kết hợp các nguyên tắc giảng dạy truyền thống với những phương pháp số đổi mới. Đam mê của tôi là tạo ra các khóa học không chỉ hấp dẫn và giàu thông tin mà còn dễ tiếp cận với người học trong thời đại số này.

Chủ đề

Liên quan