Bài viết này là Phần 4 trong loạt bài về độ phức tạp NULL. Trong các bài viết trước (Phần 1, Phần 2 và Phần 3), tôi đã trình bày ý nghĩa của NULL như một điểm đánh dấu cho một giá trị bị thiếu, cách NULL hoạt động trong các phép so sánh và trong các phần tử truy vấn khác và các tính năng xử lý NULL tiêu chuẩn không chưa có sẵn trong T-SQL. Tháng này, tôi đề cập đến sự khác biệt giữa cách xác định ràng buộc duy nhất trong tiêu chuẩn ISO / IEC SQL và cách nó hoạt động trong T-SQL. Tôi cũng sẽ cung cấp các giải pháp tùy chỉnh mà bạn có thể triển khai nếu bạn cần chức năng tiêu chuẩn.
Ràng buộc DUY NHẤT tiêu chuẩn
SQL Server xử lý NULL giống như các giá trị không phải NULL nhằm mục đích thực thi một ràng buộc duy nhất. Nghĩa là, ràng buộc duy nhất trên T được thỏa mãn nếu và chỉ khi không tồn tại hai hàng R1 và R2 của T sao cho R1 và R2 có cùng tổ hợp các giá trị NULL và không phải NULL trong các cột duy nhất. Ví dụ:giả sử rằng bạn xác định một ràng buộc duy nhất trên col1, là một cột NULLable của kiểu dữ liệu INT. Nỗ lực sửa đổi bảng theo cách dẫn đến nhiều hơn một hàng có NULL trong col1 sẽ bị từ chối, giống như sửa đổi dẫn đến nhiều hơn một hàng có giá trị 1 trong col1 sẽ bị từ chối.
Giả sử rằng bạn xác định ràng buộc duy nhất tổng hợp trên sự kết hợp của cột INT NULLable col1 và col2. Nỗ lực sửa đổi bảng theo cách có thể dẫn đến nhiều hơn một lần xuất hiện của bất kỳ kết hợp giá trị (col1, col2) nào sau đây sẽ bị từ chối:(NULL, NULL), (3, NULL), (NULL, 300 ), (1, 100).
Vì vậy, như bạn có thể thấy, việc triển khai T-SQL của ràng buộc duy nhất xử lý NULL giống như các giá trị không phải NULL nhằm mục đích thực thi tính duy nhất.
Nếu bạn muốn xác định khóa ngoại trên một số bảng X tham chiếu đến một số bảng Y, bạn phải thực thi tính duy nhất trên (các) cột được tham chiếu bằng một trong các tùy chọn sau:
- Khóa chính
- Ràng buộc duy nhất
- Chỉ mục duy nhất không được lọc
Khóa chính không được phép trên các cột NULLable. Cả ràng buộc duy nhất (tạo chỉ mục bên dưới bìa) và chỉ mục duy nhất được tạo rõ ràng đều được phép trên các cột NULLable và thực thi tính duy nhất của chúng trong T-SQL bằng cách sử dụng logic đã nói ở trên. Bảng tham chiếu được phép có các hàng có NULL trong cột tham chiếu, bất kể bảng được tham chiếu có hàng có NULL trong cột được tham chiếu hay không. Ý tưởng là để hỗ trợ một mối quan hệ tùy chọn. Một số hàng trong bảng tham chiếu có thể là những hàng không liên quan đến bất kỳ hàng nào trong bảng được tham chiếu. Bạn sẽ triển khai điều này bằng cách sử dụng NULL trong cột tham chiếu.
Để chứng minh việc triển khai T-SQL của một ràng buộc duy nhất, hãy chạy đoạn mã sau, mã này sẽ tạo một bảng có tên T3 với một ràng buộc duy nhất được xác định trên cột NULLable INT cột col1 và điền nó bằng một vài hàng mẫu:
SỬ DỤNG tempdb; ĐI DROP BẢNG NẾU TỒN TẠI dbo.T3; ĐI TẠO BẢNG dbo.T3 (col1 INT NULL, col2 INT NULL, CONSTRAINT UNQ_T3 UNIQUE (col1)); CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (1, 100), (2, -1), (NULL, -1), (3, 300);
Sử dụng mã sau để truy vấn bảng:
CHỌN * TỪ dbo.T3;
Truy vấn này tạo ra kết quả sau:
col1 col2 ----------- ----------- 1 1002 -1NULL -13 300
Cố gắng chèn hàng thứ hai có NULL trong cột1:
CHÈN VÀO GIÁ TRỊ dbo.T3 (col1, col2) (NULL, 400);
Nỗ lực này bị từ chối và bạn gặp lỗi sau:
Msg 2627, Cấp độ 14, Trạng thái 1Vi phạm ràng buộc KEY DUY NHẤT 'UNQ_T3'. Không thể chèn khóa trùng lặp trong đối tượng 'dbo.T3'. Giá trị khóa trùng lặp là (
Định nghĩa ràng buộc duy nhất tiêu chuẩn hơi khác một chút so với phiên bản T-SQL. Sự khác biệt chính liên quan đến việc xử lý NULL. Dưới đây là định nghĩa ràng buộc duy nhất từ tiêu chuẩn:
“Ràng buộc duy nhất đối với T được thỏa mãn nếu và chỉ khi không tồn tại hai hàng R1 và R2 của T sao cho R1 và R2 có cùng giá trị không NULL trong các cột duy nhất.”
Vì vậy, bảng T có ràng buộc duy nhất trên col1 sẽ cho phép nhiều hàng có NULL trong col1, nhưng không cho phép nhiều hàng có cùng giá trị không NULL trong col1.
Điều phức tạp hơn một chút để giải thích là những gì xảy ra theo tiêu chuẩn với một ràng buộc duy nhất tổng hợp. Giả sử rằng bạn có một ràng buộc duy nhất được xác định trên (col1, col2). Bạn có thể có nhiều hàng với (NULL, NULL), nhưng bạn không thể có nhiều hàng với (3, NULL), giống như bạn không thể có nhiều hàng với (1, 100). Tương tự, bạn không thể có nhiều hàng với (NULL, 300). Vấn đề là bạn không được phép có nhiều hàng có cùng giá trị không phải NULL trong các cột duy nhất. Đối với khóa ngoại, bạn có thể có bất kỳ số hàng nào trong bảng tham chiếu với NULL trong tất cả các cột tham chiếu, bất kể những gì tồn tại trong bảng được tham chiếu. Các hàng như vậy không liên quan đến bất kỳ hàng nào trong bảng được tham chiếu (mối quan hệ tùy chọn). Tuy nhiên, nếu bạn có bất kỳ giá trị nào không phải NULL trong bất kỳ cột nào trong số các cột tham chiếu, thì phải tồn tại một hàng trong bảng được tham chiếu có cùng các giá trị không phải NULL trong các cột được tham chiếu.
Giả sử rằng bạn có cơ sở dữ liệu trong nền tảng hỗ trợ ràng buộc duy nhất tiêu chuẩn và bạn cần di chuyển cơ sở dữ liệu đó sang SQL Server. Bạn có thể gặp vấn đề với việc thực thi các ràng buộc duy nhất trong SQL Server nếu các cột duy nhất hỗ trợ NULL. Dữ liệu được coi là hợp lệ trong hệ thống nguồn có thể được coi là không hợp lệ trong SQL Server. Trong các phần sau, tôi sẽ khám phá một số cách giải quyết có thể có trong SQL Server.
Giải pháp 1, sử dụng chỉ mục được lọc hoặc chế độ xem được lập chỉ mục
Một giải pháp phổ biến trong T-SQL để thực thi chức năng ràng buộc duy nhất tiêu chuẩn khi chỉ có một cột mục tiêu liên quan là sử dụng một chỉ mục được lọc duy nhất chỉ lọc các hàng mà cột mục tiêu không phải là NULL. Đoạn mã sau loại bỏ ràng buộc duy nhất hiện có khỏi T3 và triển khai một chỉ mục như vậy:
ALTER TABLE dbo.T3 DROP CONSTRAINT UNQ_T3; TẠO CHỈ SỐ KHÔNG ĐƯỢC ĐIỀU CHỈNH DUY NHẤT idx_col1_notnull TRÊN dbo.T3 (col1) TRONG ĐÓ col1 KHÔNG ĐỦ;
Vì chỉ mục chỉ lọc các hàng trong đó col1 không phải là NULL, thuộc tính UNIQUE của nó chỉ được thực thi trên các giá trị col1 không phải NULL.
Nhớ lại rằng T3 đã có một hàng với NULL trong col1. Để kiểm tra giải pháp này, hãy sử dụng mã sau để thêm hàng thứ hai có NULL trong cột1:
CHÈN VÀO GIÁ TRỊ dbo.T3 (col1, col2) (NULL, 400);
Mã này chạy thành công.
Nhớ lại rằng T3 đã có một hàng với giá trị 1 trong col1. Chạy mã sau để cố gắng thêm hàng thứ hai với 1 trong cột1:
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (1, 500);
Như mong đợi, nỗ lực này không thành công với lỗi sau:
Msg 2601, Cấp 14, Trạng thái 1Không thể chèn hàng khóa trùng lặp trong đối tượng 'dbo.T3' với chỉ mục duy nhất 'idx_col1_notnull'. Giá trị khóa trùng lặp là (1).
Sử dụng mã sau để truy vấn T3:
CHỌN * TỪ dbo.T3;
Mã này tạo kết quả đầu ra sau đây hiển thị hai hàng có NULL trong cột1:
col1 col2 ----------- ----------- 1 1002 -1NULL -13 300NULL 400
Giải pháp này hoạt động tốt khi bạn chỉ cần thực thi tính duy nhất trên một cột và khi bạn không cần thực thi tính toàn vẹn tham chiếu bằng khóa ngoại trỏ đến cột đó.
Vấn đề với khóa ngoại là SQL Server yêu cầu một khóa chính hoặc một ràng buộc duy nhất hoặc một chỉ mục không được lọc duy nhất được xác định trên cột được tham chiếu. Nó không hoạt động khi chỉ có một chỉ mục được lọc duy nhất được xác định trên cột được tham chiếu. Hãy thử tạo một bảng có khóa ngoại tham chiếu T3.col1. Đầu tiên, sử dụng mã sau để tạo bảng T3:
DROP TABLE NẾU TỒN TẠI dbo.T3FK; ĐI TẠO BẢNG dbo.T3FK (id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR (10) NOT NULL);
Sau đó, hãy thử chạy mã sau để cố gắng thêm khóa ngoại trỏ từ T3FK.col1 đến T3.col1:
BẢNG ALTER dbo.T3FK ADD CONSTRAINT FK_T3_T3FK FOREIGN KEY (col1) TÀI LIỆU THAM KHẢO dbo.T3 (col1);
Nỗ lực này không thành công với lỗi sau:
Msg 1776, Cấp 16, Trạng thái 0Không có khóa chính hoặc khóa ứng viên nào trong bảng tham chiếu 'dbo.T3' khớp với danh sách cột tham chiếu trong khóa ngoại 'FK_T3_T3FK'.
Msg 1750, Cấp 16, Trạng thái 1
Không thể tạo ràng buộc hoặc chỉ mục. Xem các lỗi trước đó.
Tại thời điểm này, hãy thả chỉ mục đã lọc hiện có để dọn dẹp:
DROP INDEX idx_col1_notnull ON dbo.T3;
Đừng bỏ bảng T3FK, vì bạn sẽ sử dụng nó trong các ví dụ sau.
Vấn đề khác với giải pháp chỉ mục được lọc, giả sử bạn không cần khóa ngoại, là nó không hoạt động khi bạn cần thực thi chức năng ràng buộc duy nhất tiêu chuẩn trên nhiều cột, chẳng hạn như trên tổ hợp (col1, col2) . Hãy nhớ rằng ràng buộc duy nhất tiêu chuẩn không cho phép các kết hợp giá trị không NULL trùng lặp trong các cột duy nhất. Để triển khai logic này với một chỉ mục đã lọc, bạn chỉ cần lọc các hàng mà bất kỳ cột nào trong số các cột duy nhất không phải là NULL. Được phân loại theo cách khác, bạn chỉ cần lọc các hàng không có NULL trong tất cả các cột duy nhất. Thật không may, các chỉ mục được lọc chỉ cho phép các biểu thức rất đơn giản. Họ không hỗ trợ HOẶC, KHÔNG hoặc thao tác trên các cột. Vì vậy, không có định nghĩa chỉ mục nào sau đây hiện được hỗ trợ:
TẠO CHỈ SỐ KHÔNG ĐƯỢC ĐIỀU CHỈNH DUY NHẤT idx_customunique TRÊN dbo.T3 (col1, col2) TRONG ĐÓ col1 KHÔNG ĐẦY ĐỦ HOẶC col2 KHÔNG ĐẦY ĐỦ; TẠO CHỈ SỐ KHÔNG ĐƯỢC ĐIỀU CHỈNH DUY NHẤT idx_customunique TRÊN dbo.T3 (col1, col2) KHÔNG CÓ (col1 LÀ NULL VÀ col2 LÀ NULL); TẠO CHỈ SỐ KHÔNG ĐƯỢC ĐIỀU CHỈNH DUY NHẤT idx_customunique TRÊN dbo.T3 (col1, col2) TẠI ĐÂU COALESCE (col1, col2) KHÔNG ĐẦY ĐỦ;
Cách giải quyết trong trường hợp như vậy là tạo chế độ xem được lập chỉ mục dựa trên truy vấn trả về col1 và col2 từ T3 với một trong các mệnh đề WHERE ở trên, với chỉ mục nhóm duy nhất trên (col1, col2), như sau:
TẠO CHẾ ĐỘ XEM dbo.T3CustomUnique VỚI SCHEMABINDINGAS CHỌN col1, col2 TỪ dbo.T3 TRONG ĐÓ col1 KHÔNG ĐẦY HOẶC col2 KHÔNG ĐẦY ĐỦ; HÃY TẠO CHỈ SỐ ĐƯỢC ĐIỀU CHỈNH DUY NHẤT idx_col1_col2 TRÊN dbo.T3CustomUnique (col1, col2); ĐIBạn sẽ được phép thêm nhiều hàng có (NULL, NULL) trong (col1, col2), nhưng bạn sẽ không được phép thêm nhiều lần xuất hiện của các tổ hợp giá trị không phải NULL trong (col1, col2), chẳng hạn như (3 , NULL) hoặc (NULL, 300) hoặc (1, 100). Tuy nhiên, giải pháp này không hỗ trợ khóa ngoại.
Tại thời điểm này, hãy chạy mã sau để dọn dẹp:
DROP XEM NẾU TỒN TẠI dbo.T3CustomUnique;Giải pháp 2, sử dụng khóa thay thế và cột được tính toán
Các giải pháp có chỉ mục được lọc và chế độ xem được lập chỉ mục là tốt miễn là bạn không cần hỗ trợ khóa ngoại. Nhưng nếu bạn cần thực thi tính toàn vẹn của tham chiếu thì sao? Một tùy chọn là tiếp tục sử dụng chỉ mục đã lọc hoặc giải pháp chế độ xem được lập chỉ mục để thực thi tính duy nhất và sử dụng trình kích hoạt để thực thi tính toàn vẹn của tham chiếu. Tuy nhiên, tùy chọn này khá tốn kém.
Một tùy chọn khác là sử dụng một giải pháp hoàn toàn khác cho phần tính duy nhất hỗ trợ khóa ngoại. Giải pháp liên quan đến việc thêm hai cột vào bảng được tham chiếu (T3 trong trường hợp của chúng tôi). Một cột được gọi là id là một khóa thay thế có thuộc tính nhận dạng. Một cột khác được gọi là cờ là một cột được tính toán liên tục trả về id khi col1 là NULL và 0 khi nó không phải là NULL. Sau đó, bạn thực thi một ràng buộc duy nhất đối với sự kết hợp của col1 và cờ. Đây là mã để thêm hai cột và ràng buộc duy nhất:
ALTER TABLE dbo.T3 THÊM id INT NOT NULL IDENTITY, gắn cờ NHƯ TRƯỜNG HỢP KHI col1 KHÔNG ĐỦ THÌ id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3_col1_flag UNIQUE (col1, flag);Sử dụng mã sau để truy vấn T3:
CHỌN * TỪ dbo.T3;Mã này tạo ra kết quả sau:
col1 col2 id flag ----------- ----------- ----------- ---------- -1 100 1 02 -1 2 0NULL -1 3 33 300 4 0NULL 400 5 5Đối với bảng tham chiếu (trong trường hợp của chúng tôi là T3FK), bạn thêm một cột được tính toán có tên là cờ luôn được đặt thành 0 và khóa ngoại được xác định trên (col1, cờ) trỏ đến các cột duy nhất của T3 (col1, cờ), như vậy :
ALTER TABLE dbo.T3FK ADD flag AS 0 PERSISTED, CONSTRAINT FK_T3_T3FK FOREIGN KEY (col1, flag) TÀI LIỆU THAM KHẢO dbo.T3 (col1, flag);Hãy thử nghiệm giải pháp này.
Cố gắng thêm các hàng sau:
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3FK (col1, col2, othercol) (1, 100, 'A'), (2, -1, 'B'), (3, 300, 'C');Các hàng này được thêm thành công, như chúng nên làm, vì tất cả đều có các hàng được tham chiếu tương ứng.
Truy vấn bảng T3FK:
CHỌN * TỪ dbo.T3FK;Bạn nhận được kết quả sau:
id col1 col2 othercol flag ----------- ----------- ------------- - ----------- 1 1 100 A 02 2 -1 B 03 3 300 C 0Cố gắng thêm một hàng không có hàng tương ứng trong bảng được tham chiếu:
CHÈN VÀO GIÁ TRỊ dbo.T3FK (col1, col2, othercol) (4, 400, 'D');Nỗ lực bị từ chối, vì lẽ ra, với lỗi sau:
Msg 547, Mức 16, Trạng thái 0
Câu lệnh INSERT xung đột với ràng buộc FOREIGN KEY "FK_T3_T3FK". Xung đột xảy ra trong cơ sở dữ liệu "TSQLV5", bảng "dbo.T3".Thử thêm một hàng vào T3FK với NULL trong col1:
CHÈN VÀO GIÁ TRỊ dbo.T3FK (col1, col2, othercol) (NULL, NULL, 'E');Hàng này được coi là không liên quan đến bất kỳ hàng nào trong T3FK (mối quan hệ tùy chọn) và theo tiêu chuẩn, phải được phép bất kể NULL có tồn tại trong bảng được tham chiếu ở col1 hay không. T-SQL không hỗ trợ tình huống này và hàng được thêm thành công.
Truy vấn bảng T3FK:
CHỌN * TỪ dbo.T3FK;Mã này tạo ra kết quả sau:
id col1 col2 othercol flag ----------- ----------- ------------- - ----------- 1 1 100 A 02 2 -1 B 03 3 300 C 05 NULL NULL E 0Giải pháp hoạt động tốt khi bạn cần thực thi chức năng duy nhất tiêu chuẩn trên một cột duy nhất. Nhưng nó có một vấn đề khi bạn cần thực thi tính duy nhất trên nhiều cột. Để giải thích vấn đề, trước tiên hãy thả bảng T3 và T3FK:
DROP TABLE NẾU TỒN TẠI dbo.T3FK, dbo.T3;Sử dụng mã sau để tạo lại T3 với ràng buộc duy nhất tổng hợp trên (col1, col2, flag):
TẠO BẢNG dbo.T3 (col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, gắn cờ NHƯ TRƯỜNG HỢP KHI col1 LÀ NULL VÀ col2 LÀ KHÔNG ĐỦ THEN id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE (col1, col2, flag ));Lưu ý rằng cờ được đặt thành id khi cả col1 và col2 đều là NULL và ngược lại là 0.
Ràng buộc duy nhất tự nó hoạt động tốt.
Chạy mã sau để thêm một vài hàng vào T3, bao gồm nhiều lần xuất hiện của (NULL, NULL) trong (col1, col2):
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (1, 100), (1, 200), (NULL, NULL), (NULL, NULL);Những hàng này được thêm thành công như chúng cần.
Hãy thử thêm hai lần xuất hiện của (1, NULL) trong (col1, col2):
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (1, NULL), (1, NULL);Nỗ lực này không thành công với lỗi sau:
Msg 2627, Cấp độ 14, Trạng thái 1
Vi phạm ràng buộc KEY DUY NHẤT 'UNQ_T3'. Không thể chèn khóa trùng lặp trong đối tượng 'dbo.T3'. Giá trị khóa trùng lặp là (1,, 0). Hãy thử thêm hai lần xuất hiện của (NULL, 100) trong (col1, col2):
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (NULL, 100), (NULL, 100);Nỗ lực này cũng không thành công với lỗi sau:
Msg 2627, Cấp độ 14, Trạng thái 1
Vi phạm ràng buộc KEY DUY NHẤT 'UNQ_T3'. Không thể chèn khóa trùng lặp trong đối tượng 'dbo.T3'. Giá trị khóa trùng lặp là (, 100, 0). Hãy thử thêm hai hàng sau để không có vi phạm nào xảy ra:
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (3, NULL), (NULL, 300);Các hàng này đã được thêm thành công.
Truy vấn bảng T3 tại thời điểm này:
CHỌN * TỪ dbo.T3;Bạn nhận được kết quả sau:
col1 col2 id flag ----------- ----------- ----------- ---------- -1 100 1 01 200 2 0 NULL 3 3NULL NULL 4 43 NULL 9 0NULL 300 10 0Cho đến nay rất tốt.
Tiếp theo, hãy chạy mã sau để tạo bảng T3FK với khóa ngoại kết hợp tham chiếu đến các cột duy nhất của T3:
TẠO BẢNG dbo.T3FK (id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR (10) NOT NULL, cờ AS 0 PERSISTED, CONSTRAINT FK_T3_T3FK FOREIGN KEY (col1, col2, flag) ) TÀI LIỆU THAM KHẢO dbo.T3 (col1, col2, flag));Giải pháp này tự nhiên cho phép thêm các hàng vào T3FK với (NULL, NULL) trong (col1, col2). Vấn đề là nó cũng cho phép thêm các hàng một NULL trong col1 hoặc col2, ngay cả khi cột khác không phải là NULL và bảng được tham chiếu T3 không có tổ hợp phím như vậy. Ví dụ:hãy thử thêm hàng sau vào T3FK:
CHÈN VÀO GIÁ TRỊ dbo.T3FK (col1, col2, othercol) (5, NULL, 'A');Hàng này được thêm thành công mặc dù không có hàng liên quan nào trong T3. Theo tiêu chuẩn, hàng này không được phép.
Quay lại bảng vẽ…
Giải pháp 3, sử dụng khóa thay thế và cột được tính toán
Vấn đề với giải pháp trước (Giải pháp 2) nảy sinh khi bạn cần hỗ trợ khóa ngoại tổng hợp. Nó cho phép các hàng trong bảng tham chiếu có NULL trong danh sách một cột tham chiếu, ngay cả khi có giá trị không phải NULL trong các cột tham chiếu khác và không có hàng liên quan nào trong bảng được tham chiếu. Để giải quyết vấn đề này, bạn có thể sử dụng một biến thể của giải pháp trước đó, mà chúng tôi sẽ gọi là Giải pháp 3.
Đầu tiên, sử dụng mã sau để loại bỏ các bảng hiện có:
DROP TABLE NẾU TỒN TẠI dbo.T3FK, dbo.T3;Trong giải pháp mới trong bảng được tham chiếu (T3 trong trường hợp của chúng tôi), bạn vẫn sử dụng cột khóa thay thế id dựa trên danh tính. Bạn cũng sử dụng một cột được tính toán liên tục được gọi là unqpath. Khi tất cả các cột duy nhất (col1 và col2 trong ví dụ của chúng tôi) là NULL, bạn đặt unqpath thành biểu diễn chuỗi ký tự của id ( không có dấu phân tách ). Khi bất kỳ cột nào trong số các cột duy nhất không phải là NULL, bạn đặt unqpath thành biểu diễn chuỗi ký tự của danh sách các giá trị cột duy nhất được phân tách bằng cách sử dụng hàm CONCAT. Hàm này thay thế một NULL bằng một chuỗi rỗng. Điều quan trọng là đảm bảo sử dụng dấu phân tách thường không thể xuất hiện trong chính dữ liệu. Ví dụ:với các giá trị col1 và col2 số nguyên, bạn chỉ có các chữ số, vì vậy bất kỳ dấu phân tách nào khác với một chữ số sẽ hoạt động. Trong ví dụ của mình, tôi sẽ sử dụng dấu chấm (.). Sau đó, bạn thực thi một ràng buộc duy nhất trên unqpath. Bạn sẽ không bao giờ có xung đột giữa giá trị đường dẫn unqpath khi tất cả các cột duy nhất là NULL (được đặt thành id) so với khi bất kỳ cột nào trong số các cột duy nhất không phải là NULL vì trong trường hợp trước, đường dẫn không chứa dấu phân tách và trong trường hợp sau thì . Hãy nhớ rằng bạn sẽ sử dụng Giải pháp 3 khi bạn có một trường hợp khóa tổng hợp và có thể thích Giải pháp 2, đơn giản hơn, khi bạn có một trường hợp khóa một cột. Nếu bạn cũng muốn sử dụng Giải pháp 3 với khóa một cột chứ không phải Giải pháp 2, chỉ cần đảm bảo rằng bạn thêm dấu phân tách khi cột duy nhất không phải là NULL mặc dù chỉ có một giá trị liên quan. Bằng cách này, bạn sẽ không có xung đột khi id ở một hàng trong đó col1 là NULL bằng với col1 trong một hàng khác, vì id ở hàng trước sẽ không có dấu phân tách và id ở hàng sau sẽ có.
Đây là mã để tạo T3 với các bổ sung nói trên:
CREATE TABLE dbo.T3 (col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, unqpath AS CASE KHI col1 LÀ NULL VÀ col2 LÀ NULL THEN CAST (id AS VARCHAR (10)) ELSE CONCAT (CAST (col1) AS VARCHAR (11)), '.', CAST (col2 AS VARCHAR (11))) END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE (unqpath));Trước khi xử lý khóa ngoại và bảng tham chiếu, hãy kiểm tra ràng buộc duy nhất. Hãy nhớ rằng, nó phải không cho phép kết hợp trùng lặp các giá trị không phải NULL trong các cột duy nhất, nhưng nó phải cho phép nhiều lần xuất hiện tất cả-NULL trong các cột duy nhất.
Chạy mã sau để thêm một vài hàng, bao gồm hai lần xuất hiện của (NULL, NULL) trong (col1, col2):
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (1, 100), (1, 200), (NULL, NULL), (NULL, NULL);Mã này hoàn thành thành công như nó cần.
Cố gắng thêm hai lần xuất hiện của (1, NULL) trong (col1, col2):
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (1, NULL), (1, NULL);Mã này không thành công với lỗi sau vì nó sẽ xảy ra:
Msg 2627, Cấp độ 14, Trạng thái 1
Vi phạm ràng buộc KEY DUY NHẤT 'UNQ_T3'. Không thể chèn khóa trùng lặp trong đối tượng 'dbo.T3'. Giá trị khóa trùng lặp là (1).Tương tự, nỗ lực sau cũng bị từ chối:
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (NULL, 100), (NULL, 100);Bạn gặp lỗi sau:
Msg 2627, Cấp độ 14, Trạng thái 1
Vi phạm ràng buộc KEY DUY NHẤT 'UNQ_T3'. Không thể chèn khóa trùng lặp trong đối tượng 'dbo.T3'. Giá trị khóa trùng lặp là (.100).Chạy mã sau để thêm một vài hàng nữa:
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1, col2) (3, NULL), (NULL, 300);Mã này chạy thành công như bình thường.
Tại thời điểm này, hãy truy vấn T3:
CHỌN * TỪ dbo.T3;Bạn nhận được kết quả sau:
col1 col2 id unqpath ----------- ----------- ----------- ------------- 1 100 1 1.1001 200 2 1.200 NULL 3 3NULL NULL 4 43 NULL 9 3.NULL 300 10 .300Quan sát các giá trị unqpath và đảm bảo rằng bạn hiểu logic đằng sau việc xây dựng chúng và sự khác biệt giữa trường hợp tất cả các cột duy nhất là NULL (không có dấu phân tách) so với khi ít nhất một cột không phải là NULL (tồn tại dấu phân tách).
Đối với bảng tham chiếu, T3FK; bạn cũng xác định một cột được tính toán được gọi là unqpath, nhưng trong trường hợp tất cả các cột tham chiếu là NULL, bạn đặt cột thành NULL — không phải id. Khi bất kỳ cột nào trong số các cột tham chiếu không phải là NULL, bạn xây dựng cùng một danh sách các giá trị được phân tách giống như bạn đã làm trong T3. Sau đó, bạn xác định một khóa ngoại trên T3FK.unqpath trỏ tới T3.unqpath, như sau:
CREATE TABLE dbo.T3FK (id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR (10) NOT NULL, unqpath AS TRƯỜNG HỢP KHI col1 LÀ NULL VÀ col2 LÀ NULL THEN NULL ELSE CONCAT (CAST (col1 AS VARCHAR (11)), '.', CAST (col2 AS VARCHAR (11))) END PERSISTED, CONSTRAINT FK_T3_T3FK FOREIGN KEY (unqpath) TÀI LIỆU THAM KHẢO dbo.T3 (unqpath));Khóa ngoại này sẽ từ chối các hàng trong T3FK nơi bất kỳ cột nào trong số các cột tham chiếu không phải là NULL và không có hàng liên quan nào trong bảng được tham chiếu T3, như lần thử sau cho thấy:
CHÈN VÀO GIÁ TRỊ dbo.T3FK (col1, col2, othercol) (5, NULL, 'A');Mã này tạo ra lỗi sau:
Msg 547, Mức 16, Trạng thái 0
Câu lệnh INSERT xung đột với ràng buộc FOREIGN KEY "FK_T3_T3FK". Xung đột xảy ra trong cơ sở dữ liệu "TSQLV5", bảng "dbo.T3", cột 'unqpath'.Giải pháp này sẽ áp dụng cho các hàng trong T3FK nơi bất kỳ cột nào trong số các cột tham chiếu không phải là NULL miễn là hàng có liên quan trong T3 tồn tại, cũng như các hàng có NULL trong tất cả các cột tham chiếu, vì các hàng như vậy được coi là không liên quan đến bất kỳ hàng nào trong T3. Đoạn mã sau thêm các hàng hợp lệ như vậy vào T3FK:
CHÈN VÀO CÁC GIÁ TRỊ dbo.T3FK (col1, col2, othercol) (1, 100, 'A'), (1, 200, 'B'), (3, NULL, 'C'), (NULL, 300 , 'D'), (NULL, NULL, 'E'), (NULL, NULL, 'F');Mã này hoàn tất thành công.
Chạy mã sau để truy vấn T3FK:
CHỌN * TỪ dbo.T3FK;Bạn nhận được kết quả sau:
id col1 col2 othercol unqpath ----------- ------------- ---------------- - ----------------------- 2 1 100 A 1.1003 1 200 B 1.2004 3 NULL C 3.5 NULL 300 D.3006 NULL NULL E NULL7 NULL NULL F NULLVì vậy, cần một chút sáng tạo, nhưng bây giờ bạn có một giải pháp cho ràng buộc duy nhất tiêu chuẩn, bao gồm hỗ trợ khóa ngoại.
Kết luận
Bạn sẽ nghĩ rằng một ràng buộc duy nhất là một tính năng đơn giản, nhưng nó có thể hơi phức tạp khi bạn cần hỗ trợ NULL trong các cột duy nhất. Nó trở nên phức tạp hơn khi bạn cần triển khai chức năng ràng buộc duy nhất tiêu chuẩn trong T-SQL, vì cả hai sử dụng các quy tắc khác nhau về cách chúng xử lý NULL. Trong bài viết này, tôi đã giải thích sự khác biệt giữa hai và cung cấp các cách giải quyết hoạt động trong T-SQL. Bạn có thể sử dụng chỉ mục được lọc đơn giản khi cần thực thi tính duy nhất trên một cột NULLable và bạn không cần hỗ trợ khóa ngoại tham chiếu đến cột đó. Tuy nhiên, nếu bạn cần hỗ trợ khóa ngoại hoặc ràng buộc duy nhất tổng hợp với chức năng tiêu chuẩn, bạn sẽ cần triển khai phức tạp hơn với khóa thay thế và cột được tính toán.