Các loại ràng buộc trong SQL Server

Ràng buộc trong SQL Server được dùng để duy trì tính nhất quán của dữ liệu, đảm bảo dữ liệu phù hợp với các qui định theo yêu cầu của bài toán. Ví dụ một database về bán hàng đòi hỏi mỗi bản ghi phải có ID sản phẩm hợp lệ, số lượng bán phải là một số nguyên và giá bán phải lớn hơn 0. Đó là các yêu cầu về tính nhất quán của dữ liệu và các ràng buộc cần được khai báo để thực thi các yêu cầu này. Do đó, ràng buộc giúp ngăn chặn dữ liệu không hợp lệ và chỉ cho phép dữ liệu hợp lệ được lưu vào database.
SQL Server cung cấp các loại ràng buộc sau:

PRIMARY KEY: khóa chính của bảng, là định danh duy nhất cho mỗi bản ghi trong bảng. Nó đòi hỏi cột (hoặc các cột) tạo thành khóa chính phải thỏa mãn hai điều kiện: không NULL và mỗi giá trị phải duy nhất trong toàn bảng. Mỗi bảng chỉ cho phép tối đa một khóa chính và theo nguyên tắc thiết kế, mỗi bảng đều cần có khóa chính. Có ba cách khai báo khóa chính:

--Cách 1
CREATE TABLE dbo.Bang(
Cot_1 INT NOT NULL PRIMARY KEY,
Cot_2 VARCHAR(100),
...
)

--Cách 2
CREATE TABLE dbo.Bang(
Cot_1 INT NOT NULL,
Cot_2 VARCHAR(100),
...
CONSTRAINT PK_Bang PRIMARY KEY (Cot_1, Cot_2)
)

--Cách 3, thêm ràng buộc cho bảng đã có sẵn
ALTER TABLE dbo.Bang ADD CONSTRAINT PK_Bang PRIMARY KEY (Cot_1, Cot_2)

Khi có khai báo khóa chính, SQL Server tự động tạo một unique index để thực thi tính duy nhất. Đây là cách làm tối ưu để kiểm tra xem một giá trị có duy nhất hay không. Mỗi khi có giá trị mới được thêm vào hay được cập nhật, hệ thống sẽ định vị trên cây index để tìm vị trí thích hợp cho nó. Nếu giá trị này đã xuất hiện, hệ thống sẽ văng ngược trở ra và báo lỗi. Ngược lại, giá trị mới được lưu vào cây index.
Index được tạo cùng với khóa chính một cách mặc định là clustered index. Bạn có thể chỉ định nó là non-clustered khi cần thiết, nhưng thông thường bạn nên để ở chế độ mặc định.
Bạn nên chọn trường có kích thước nhỏ làm khóa chính. Khi có nhiều trường kết hợp với nhau xác định định danh của bản ghi (ví dụ ba trường tên, ngày sinh và nguyên quán của bảng thí sinh), bạn có thể tạo thêm một trường THISINH_ID kiểu INTEGER làm đại diện và dùng nó làm khóa chính, và đưa ba trường kia vào một khóa duy nhất (xem phần dưới đây).

UNIQUE KEY CONSTRAINT: ràng buộc duy nhất, yêu cầu các giá trị trong cột phải khác nhau. Ràng buộc này cho phép cột hoặc các cột cấu thành khóa phải là NULL, tuy nhiên chỉ một giá trị NULL được phép xuất hiện (về khác biệt giữa định nghĩa khóa duy nhất của ANSI và SQL Server, mời bạn đọc thêm bài Tạo Ràng Buộc Duy Nhất Bằng Filtered Index). Bạn có thể tạo nhiều ràng buộc duy nhất trên cùng một bảng. Ràng buộc duy nhất cũng kéo theo một unique index giống như với khóa chính, và cơ chế kiểm tra tính duy nhất cũng giống như vậy. Điểm khác là unique index được tạo ở đây mặc định là non-clustered, trừ khi bạn chỉ định nó là clustered.
Bạn có thể thấy về mặt logic, ràng buộc duy nhất cộng thêm NOT NULL tương đương với khóa chính. Và ngoại trừ một vài khác biệt nhỏ, tạo một unique index trên cột cũng tương đương với tạo ràng buộc duy nhất trên cột đó.
Tạo ràng buộc duy nhất:

ALTER TABLE ThiSinh ADD CONSTRAINT UC_ThiSinh UNIQUE (Ten, NgaySinh, NguyenQuan)

FOREIGN KEY: ràng buộc khóa ngoại. Nó đòi hỏi cột chỉ được phép chứa giá trị xuất hiện trong cột khóa chính của bảng khác. Ràng buộc này đảm bảo tính toàn vẹn tham chiếu dữ liệu. Ví dụ với database bán hàng, bạn có bảng SanPham với SanPham_ID là khóa chính, và bảng BanHang có trường SanPham_ID với mục đích chứa ID của các sản phẩm có trong bảng SanPham. Khi đó, bạn cần tạo ràng buộc khóa ngoại trên trường Sanpham_ID của bảng BanHang, tham chiếu đến trường Sanpham_ID của bảng SanPham. Ràng buộc này đảm bảo:
– Không ai có thể đưa các giá trị SanPham_ID “tầm bậy” vào bảng BanHang. Các giá trị đều phải tồn tại trong bảng SanPham. Ví dụ, bảng SanPham chứa các ID từ 1 – 100; bạn không thể thêm một bản ghi vào bảng BanHang với SanPham_ID = 101. Nói cách khác, các hóa đơn bán hàng phải chứa các sản phẩm đã có trong danh mục.
– Nếu một SanPham_ID đã xuất hiện trong bảng BanHang (sản phẩm đã có giao dịch), không ai có thể xóa bản ghi của SanPham_ID đó trong bảng SanPham. Ví dụ, trong bảng SanPham có chứa sản phẩm Kindle Fire với ID = 52; sau khi có giao dịch bán hàng trên sản phẩm này, bảng BanHang sẽ xuất hiện bản ghi với SanPham_ID = 52; khi đó bạn không thể xóa bản ghi của Kindle Fire khỏi bảng SanPham, vì bản ghi kia trong bảng BanHang sẽ trở nên mồ côi (tham chiếu đến một sản phẩm ID không tồn tại).
Code:

ALTER TABLE dbo.BanHang ADD CONSTRAINT FK_SanPham_ID FOREIGN KEY (SanPham_ID) REFERENCES dbo.SanPham(SanPham_ID)

Một vài lưu ý với ràng buộc khóa ngoại:
– Tuy cột là khóa của bảng, nhưng các giá trị cho phép của nó lại được qui định từ một cột ở bảng khác. Vì thế có tên gọi khóa ngoại.
– Cột được tham chiếu phải là unique (primary key hoặc unique key).
– Bảng được tham chiếu phải nằm trong cùng database.

NOT NULL: ràng buộc này đơn giản là yêu cầu dữ liệu nhập vào cho cột phải chứa giá trị chứ không được để NULL. Khi bạn sửa lại một cột thành NOT NULL của bảng đã chứa dữ liệu (dùng ALTER TABLE), Toàn bộ các bản ghi của bảng đó sẽ được kiểm tra. Nếu hệ thống tìm thấy NULL nó sẽ báo lỗi và hủy bỏ lệnh ALTER TABLE. Lệnh sau sửa lại cột SanPham_ID thành NOT NULL:

ALTER TABLE dbo.BanHang ALTER TABLE SanPham_ID INT NOT NULL

DEFAULT: ràng buộc mặc định. Khi nhập dữ liệu cho bảng mà cột đó không được cung cấp giá trị thì giá trị mặc định sẽ được sử dụng. Ví dụ bạn có thể tạo ràng buộc DEFAULT cho trường NgayGD (ngày giao dịch) của bảng bán hàng là ngày giờ hệ thống:

ALTER TABLE dbo.BanHang ADD CONSTRAINT DF_NgayGD DEFAULT GETDATE() FOR NgayGD

CHECK: ràng buộc kiểm tra. Yêu cầu cột tương ứng phải thỏa mãn một biểu thức logic. Ví dụ, ràng buộc sau đòi hỏi cột SL (số lượng) phải lớn hơn 0:

ALTER TABLE dbo.BanHang ADD CONSTRAINT Chk_SL CHECK (SL>0)

Bạn có thể dùng hàm trong biểu thức của ràng buộc check, miễn là hàm phải thuộc loại trả về giá trị đơn (scalar function, không được dùng hàm kiểu bảng).

* Lưu ý
Bạn có thể thắc mắc, tất cả các ràng buộc trên đây đều có thể tự viết code để thực hiện. Ví dụ thay vì tạo ràng buộc duy nhất, trước khi insert bạn có thể thêm lệnh IF EXISTS để kiểm tra xem giá trị đã tồn tại hay chưa, và chỉ cho insert nếu không tồn tại. Vậy tại sao cần khai báo các ràng buộc? Lý do thứ nhất là tính đơn giản, bạn chỉ cần khai báo một lần mà không cần viết lại code. Thứ hai là đây là hàng rào cuối cùng và tin cậy để kiểm tra xem dữ liệu có hợp lệ hay không. Khi viết code bạn chỉ có thể đảm bảo cho ứng dụng của bạn, còn những ứng dụng khác cùng truy nhập vào database sẽ nằm ngoài tầm kiểm soát của bạn. Thậm chí cùng một ứng dụng khi được viết vào những thời điểm khác nhau, bạn cũng không thể nhớ hết mỗi cột tuân theo những ràng buộc nào. Trong khi những ràng buộc được khai báo trong database nằm ngay bên ngoài vỏ của dữ liệu, và thực thi các kiểm tra khi dữ liệu đi vào dù bằng đường nào. Lý do nữa là ràng buộc có thể giúp tối ưu hóa thực hiện câu lệnh. Ví dụ một cột có ràng buộc CHECK giá trị phải lớn hơn 0; khi gặp câu lệnh tìm trên cột đó với giá trị âm, hệ thống sẽ tức thì trả về 0 bản ghi thay vì phải tìm từng bản ghi trong bảng.