Làm cách nào để tạo một ràng buộc duy nhất cũng cho phép null?


620

Tôi muốn có một ràng buộc duy nhất trên một cột mà tôi sẽ điền vào GUID. Tuy nhiên, dữ liệu của tôi chứa giá trị null cho các cột này. Làm cách nào để tạo ràng buộc cho phép nhiều giá trị null?

Đây là một kịch bản ví dụ . Hãy xem xét lược đồ này:

CREATE TABLE People (
  Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY,
  Name NVARCHAR(250) NOT NULL,
  LibraryCardId UNIQUEIDENTIFIER NULL,
  CONSTRAINT UQ_People_LibraryCardId UNIQUE (LibraryCardId)
)

Sau đó, xem mã này cho những gì tôi đang cố gắng để đạt được:

-- This works fine:
INSERT INTO People (Name, LibraryCardId) 
 VALUES ('John Doe', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');

-- This also works fine, obviously:
INSERT INTO People (Name, LibraryCardId) 
VALUES ('Marie Doe', 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB');

-- This would *correctly* fail:
--INSERT INTO People (Name, LibraryCardId) 
--VALUES ('John Doe the Second', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');

-- This works fine this one first time:
INSERT INTO People (Name, LibraryCardId) 
VALUES ('Richard Roe', NULL);

-- THE PROBLEM: This fails even though I'd like to be able to do this:
INSERT INTO People (Name, LibraryCardId) 
VALUES ('Marcus Roe', NULL);

Tuyên bố cuối cùng thất bại với một thông báo:

Vi phạm các ràng buộc UNIITE KEY 'UQ_P People_L LibraryCardId'. Không thể chèn khóa trùng lặp vào đối tượng 'dbo.P People'.

Làm cách nào tôi có thể thay đổi lược đồ và / hoặc ràng buộc tính duy nhất để nó cho phép nhiều NULLgiá trị, trong khi vẫn kiểm tra tính duy nhất trên dữ liệu thực tế?


Vấn đề kết nối để tương thích tiêu chuẩn để bỏ phiếu cho: connect.microsoft.com/QueryServer/Feedback/Details/299229
Vadzim


Hạn chế ĐỘC ĐÁO và cho phép NULL. ? Đó là lẽ thường. Điều đó là không thể
flik 26/03/18

13
@flik, tốt hơn không nên nói đến "lẽ thường". Đó không phải là đối số hợp lệ. Đặc biệt khi xem xét đó nullkhông phải là một giá trị mà là sự thiếu vắng của giá trị. Theo tiêu chuẩn SQL, nullkhông được coi là bằng null. Vậy tại sao nhiều nullnên là một vi phạm duy nhất?
Frédéric

Câu trả lời:


144

Máy chủ SQL 2008 +

Bạn có thể tạo một chỉ mục duy nhất chấp nhận nhiều NULL với một WHEREmệnh đề. Xem câu trả lời dưới đây .

Trước SQL Server 2008

Bạn không thể tạo một ràng buộc ĐỘC ĐÁO và cho phép NULL. Bạn cần đặt giá trị mặc định là NEWID ().

Cập nhật các giá trị hiện có thành NEWID () trong đó NULL trước khi tạo ràng buộc UNIQUE.


2
và điều này sẽ hồi cứu thêm các giá trị cho các hàng hiện có, nếu vậy đây là điều tôi cần làm, cảm ơn?
Stuart

1
Bạn sẽ cần chạy câu lệnh CẬP NHẬT để đặt các giá trị hiện tại thành NEWID () trong đó trường hiện tại IS NULL
Jose Basilio

54
Nếu bạn đang sử dụng SQL Server 2008 trở lên, hãy xem câu trả lời bên dưới với hơn 100 lượt upvote. Bạn có thể thêm mệnh đề WHERE vào ràng buộc duy nhất của bạn.
Darren Griffith

1
Vấn đề này cũng ảnh hưởng đến ADO.NET DataTables. Vì vậy, ngay cả khi tôi có thể cho phép null trong trường sao lưu bằng phương thức này, DataTable sẽ không cho phép tôi lưu trữ NULL trong một cột duy nhất ở vị trí đầu tiên. Nếu bất cứ ai biết một giải pháp cho điều đó, xin vui lòng gửi nó ở đây
dotNET

6
Các bạn hãy chắc chắn rằng bạn cuộn xuống và đọc câu trả lời với 600 upvote. Nó không còn chỉ hơn 100.
Sáng

1288

Những gì bạn đang tìm kiếm thực sự là một phần của các tiêu chuẩn ANSI SQL: 92, SQL: 1999 và SQL: 2003, tức là một ràng buộc UNIQUE phải không cho phép các giá trị không phải NULL trùng lặp nhưng chấp nhận nhiều giá trị NULL.

Tuy nhiên, trong thế giới SQL Server của Microsoft, một NULL duy nhất được cho phép nhưng nhiều NULL không ...

Trong SQL Server 2008 , bạn có thể xác định một chỉ mục được lọc duy nhất dựa trên một vị từ loại trừ NULL:

CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull
ON YourTable(yourcolumn)
WHERE yourcolumn IS NOT NULL;

Trong các phiên bản trước, bạn có thể sử dụng VIEWS với vị từ KHÔNG NULL để thực thi ràng buộc.


3
đây có lẽ là cách tốt nhất để làm điều này không chắc chắn nếu có bất kỳ tác động hiệu suất? bất kỳ ai?
Simon_Weaver

3
Tôi đang cố gắng thực hiện chính xác điều này trong phiên bản SQL Server 2008 Express và tôi gặp một lỗi như sau: TẠO ĐỘC LẬP KHÔNG GIỚI HẠN UC_MailId TRÊN [SLS-CP] .dbo.MasterFileEntry (MailingId) WHERE MailingId KHÔNG phải là NULL Cấp 15, Trạng thái 1, Dòng 3 Cú pháp không chính xác gần từ khóa 'WHERE'. Nếu tôi loại bỏ mệnh đề where thì DDL chạy tốt, nhưng tất nhiên, không làm những gì tôi cần. Có ý kiến ​​gì không?
Kenneth Baltrinic

4
Trừ khi tôi nhầm, bạn không thể tạo Khóa ngoại từ Chỉ mục duy nhất giống như bạn có thể tắt một ràng buộc duy nhất. (Ít nhất SSMS đã phàn nàn với tôi khi tôi cố gắng.) Thật tuyệt khi có thể có một cột không thể luôn luôn duy nhất (khi không null) là nguồn gốc của mối quan hệ Khóa ngoài.
Núi lửa

8
Thực sự là một câu trả lời tuyệt vời. Quá tệ, nó đã bị ẩn bởi một người được chấp nhận là câu trả lời. Giải pháp này hầu như không được tôi chú ý, nhưng nó hoạt động như những điều kỳ diệu trong quá trình thực hiện của tôi bây giờ.
San hô Doe

2
Một cách khác cho SQL 2005 trở xuống là một cột được tính toán hay còn gọi là thủ thuật "Nullbuster". stackoverflow.com/a/191729/132461 Nó giúp bạn tránh làm lộn xộn cơ sở dữ liệu với một chế độ xem khác, thay vào đó bạn chỉ có một cột khác - thường được đặt tên là CộtA-Nullbuster nếu CộtA là cột mà bạn muốn là ANI nullable UNIITE. Đặt một Chỉ số ĐỘC ĐÁO (hoặc ràng buộc để thể hiện ý định kinh doanh) trên CộtA-Nullbuster và nó sẽ thực thi tính duy nhất trên
CộtA

34

SQL Server 2008 trở lên

Chỉ cần lọc một chỉ mục duy nhất:

CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;

Trong các phiên bản thấp hơn, Chế độ xem cụ thể vẫn không bắt buộc

Đối với SQL Server 2005 trở về trước, bạn có thể làm điều đó mà không cần xem. Tôi vừa thêm một ràng buộc duy nhất như bạn đang yêu cầu một trong các bảng của mình. Cho rằng tôi muốn tính duy nhất trong cột SamAccountName, nhưng tôi muốn cho phép nhiều NULL, tôi đã sử dụng một cột được vật chất hóa thay vì một khung nhìn cụ thể hóa:

ALTER TABLE dbo.Party ADD SamAccountNameUnique
   AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
   UNIQUE (SamAccountNameUnique)

Bạn chỉ cần đặt một cái gì đó vào cột được tính sẽ được đảm bảo duy nhất trên toàn bộ bảng khi cột duy nhất mong muốn thực tế là NULL. Trong trường hợp này, PartyIDlà một cột danh tính và là số sẽ không bao giờ khớp với bất kỳ SamAccountName, vì vậy nó hoạt động với tôi. Bạn có thể thử phương pháp của riêng mình, hãy chắc chắn rằng bạn hiểu miền của dữ liệu của mình để không có khả năng giao nhau với dữ liệu thực. Điều đó có thể đơn giản như việc chuẩn bị một nhân vật khác biệt như thế này:

Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))

Ngay cả khi PartyIDtrở thành phi số một ngày nào đó và có thể trùng với một SamAccountName, bây giờ nó không thành vấn đề.

Lưu ý rằng sự hiện diện của một chỉ mục bao gồm cột được tính toán khiến cho mỗi kết quả biểu thức được lưu vào đĩa với dữ liệu khác trong bảng mà DOES chiếm thêm không gian đĩa.

Lưu ý rằng nếu bạn không muốn có một chỉ mục, bạn vẫn có thể lưu CPU bằng cách làm cho biểu thức được định sẵn vào đĩa bằng cách thêm từ khóa PERSISTEDvào cuối định nghĩa biểu thức cột.

Trong SQL Server 2008 trở lên, chắc chắn sử dụng giải pháp được lọc thay thế nếu bạn có thể!

Tranh cãi

Xin lưu ý rằng một số chuyên gia cơ sở dữ liệu sẽ xem đây là trường hợp "thay thế NULL", điều này chắc chắn có vấn đề (chủ yếu là do các vấn đề xung quanh cố gắng xác định khi nào đó là giá trị thực hoặc giá trị thay thế cho dữ liệu bị thiếu ; cũng có thể có vấn đề với số lượng giá trị thay thế không phải NULL nhân lên như điên).

Tuy nhiên, tôi tin rằng trường hợp này là khác nhau. Cột được tính mà tôi thêm sẽ không bao giờ được sử dụng để xác định bất cứ điều gì. Bản thân nó không có ý nghĩa gì và mã hóa không có thông tin nào chưa được tìm thấy riêng trong các cột khác, được xác định đúng. Nó không bao giờ nên được lựa chọn hoặc sử dụng.

Vì vậy, câu chuyện của tôi là đây không phải là một NULL thay thế, và tôi đang gắn bó với nó! Vì chúng tôi thực sự không muốn giá trị không phải NULL cho bất kỳ mục đích nào ngoài mục đích lừa UNIQUEchỉ mục bỏ qua NULL, trường hợp sử dụng của chúng tôi không có vấn đề nào phát sinh với việc tạo NULL thay thế bình thường.

Tất cả những gì đã nói, tôi không có vấn đề gì với việc sử dụng chế độ xem được lập chỉ mục thay vào đó, nhưng nó mang lại một số vấn đề với nó, chẳng hạn như yêu cầu sử dụng SCHEMABINDING. Vui khi thêm một cột mới vào bảng cơ sở của bạn (tối thiểu bạn sẽ phải bỏ chỉ mục, sau đó thả chế độ xem hoặc thay đổi chế độ xem để không bị ràng buộc lược đồ). Xem danh sách đầy đủ (dài) các yêu cầu để tạo chế độ xem được lập chỉ mục trong SQL Server (2005) (cũng là các phiên bản mới hơn), (2000) .

Cập nhật

Nếu cột của bạn là số, có thể có thách thức đảm bảo rằng ràng buộc duy nhất sử dụng Coalescekhông dẫn đến va chạm. Trong trường hợp đó, có một số tùy chọn. Người ta có thể sử dụng số âm, chỉ đặt "NULL thay thế" trong phạm vi âm và "giá trị thực" chỉ trong phạm vi dương. Thay phiên, mô hình sau đây có thể được sử dụng. Trong bảng Issue(nơi IssueIDPRIMARY KEY), có thể có hoặc không có một TicketID, nhưng nếu có một, nó phải là duy nhất.

ALTER TABLE dbo.Issue ADD TicketUnique
   AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
   UNIQUE (TicketID, TicketUnique);

Nếu IssueID 1 có vé 123, UNIQUEràng buộc sẽ là các giá trị (123, NULL). Nếu IssueID 2 không có vé, nó sẽ được bật (NULL, 2). Một số suy nghĩ sẽ cho thấy ràng buộc này không thể được nhân đôi cho bất kỳ hàng nào trong bảng và vẫn cho phép nhiều NULL.


16

Đối với những người đang sử dụng Microsoft SQL Server Manager và muốn tạo một chỉ mục duy nhất nhưng không thể xóa được, bạn có thể tạo chỉ mục duy nhất như bình thường trong Thuộc tính chỉ mục cho chỉ mục mới của mình, chọn "Bộ lọc" từ bảng điều khiển bên trái, sau đó nhập bộ lọc của bạn (đó là mệnh đề where của bạn). Nó nên đọc một cái gì đó như thế này:

([YourColumnName] IS NOT NULL)

Điều này hoạt động với MSSQL 2012


Cách tạo một chỉ mục được lọc trong Microsoft SQL Server Management Studio được mô tả ở đây và hoạt động hoàn hảo: msdn.microsoft.com/en-us/l Library / cc280372.aspx
ngày

9

Khi tôi áp dụng chỉ mục duy nhất dưới đây:

CREATE UNIQUE NONCLUSTERED INDEX idx_badgeid_notnull
ON employee(badgeid)
WHERE badgeid IS NOT NULL;

mọi cập nhật không null và chèn không thành công với lỗi bên dưới:

CẬP NHẬT không thành công vì các tùy chọn SET sau có cài đặt không chính xác: 'ARITHABORT'.

Tôi tìm thấy điều này trên MSDN

THIẾT LẬP ARITHABORT phải BẬT khi bạn đang tạo hoặc thay đổi chỉ mục trên các cột được tính toán hoặc chế độ xem được lập chỉ mục. Nếu SET ARITHABORT là TẮT, TẠO, CẬP NHẬT, XÁC NHẬN và XÓA các câu lệnh trên các bảng có chỉ mục trên các cột được tính toán hoặc các khung nhìn được lập chỉ mục sẽ không thành công.

Vì vậy, để làm cho nó hoạt động chính xác, tôi đã làm điều này

Nhấp chuột phải vào [Cơ sở dữ liệu] -> Thuộc tính -> Tùy chọn -> Tùy chọn khác -> Bỏ sót -> Đã hủy bỏ số học -> đúng

Tôi tin rằng có thể thiết lập tùy chọn này trong mã bằng cách sử dụng

ALTER DATABASE "DBNAME" SET ARITHABORT ON

nhưng tôi chưa thử


6

Tạo chế độ xem chỉ chọn các NULLcột không phải và tạo UNIQUE INDEXchế độ xem:

CREATE VIEW myview
AS
SELECT  *
FROM    mytable
WHERE   mycolumn IS NOT NULL

CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn)

Lưu ý rằng bạn sẽ cần thực hiện INSERTUPDATEtrên màn hình thay vì bảng.

Bạn có thể làm điều đó với một INSTEAD OFkích hoạt:

CREATE TRIGGER trg_mytable_insert ON mytable
INSTEAD OF INSERT
AS
BEGIN
        INSERT
        INTO    myview
        SELECT  *
        FROM    inserted
END

Vì vậy, tôi cần phải thay đổi dal của tôi để chèn vào xem?
Stuart

1
Bạn có thể tạo một kích hoạt INSTEAD OF INSERT.
Quassnoi

6

Nó có thể được thực hiện trong các nhà thiết kế là tốt

Nhấp chuột phải vào Index> Properties để có cửa sổ này

chiếm lấy


Thay thế rất đẹp nếu bạn có quyền truy cập vào nhà thiết kế
Francisco

Mặc dù, như tôi vừa phát hiện ra, một khi bạn có dữ liệu trong bảng của mình, bạn không còn có thể sử dụng trình thiết kế. Dường như bỏ qua bộ lọc và mọi cập nhật bảng đã thử đều được đáp ứng với thông báo "Khóa trùng lặp không được phép"
MortimerCat

4

Có thể tạo một ràng buộc duy nhất trên Chế độ xem được lập chỉ mục theo cụm

Bạn có thể tạo Chế độ xem như thế này:

CREATE VIEW dbo.VIEW_OfYourTable WITH SCHEMABINDING AS
SELECT YourUniqueColumnWithNullValues FROM dbo.YourTable
WHERE YourUniqueColumnWithNullValues IS NOT NULL;

và các ràng buộc duy nhất như thế này:

CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE 
  ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues)

2

Có thể xem xét một INSTEAD OFkích hoạt "" và tự kiểm tra? Với một chỉ mục không phân cụm (không duy nhất) trên cột để cho phép tra cứu.


1

Như đã nêu trước đây, SQL Server không triển khai tiêu chuẩn ANSI khi nói đến UNIQUE CONSTRAINT. Có một vé trên Microsoft Connect cho điều này kể từ năm 2007. Như đã đề xuất ở đâyở đây, các tùy chọn tốt nhất cho đến ngày hôm nay là sử dụng một chỉ mục được lọc như đã nêu trong một câu trả lời khác hoặc một cột được tính toán, ví dụ:

CREATE TABLE [Orders] (
  [OrderId] INT IDENTITY(1,1) NOT NULL,
  [TrackingId] varchar(11) NULL,
  ...
  [ComputedUniqueTrackingId] AS (
      CASE WHEN [TrackingId] IS NULL
      THEN '#' + cast([OrderId] as varchar(12))
      ELSE [TrackingId_Unique] END
  ),
  CONSTRAINT [UQ_TrackingId] UNIQUE ([ComputedUniqueTrackingId])
)

1

Bạn có thể tạo một trình kích hoạt INSTEAD OF để kiểm tra các điều kiện và lỗi cụ thể nếu chúng được đáp ứng. Tạo một chỉ mục có thể tốn kém trên các bảng lớn hơn.

Đây là một ví dụ:

CREATE TRIGGER PONY.trg_pony_unique_name ON PONY.tbl_pony
 INSTEAD OF INSERT, UPDATE
 AS
BEGIN
 IF EXISTS(
    SELECT TOP (1) 1 
    FROM inserted i
    GROUP BY i.pony_name
    HAVING COUNT(1) > 1     
    ) 
     OR EXISTS(
    SELECT TOP (1) 1 
    FROM PONY.tbl_pony t
    INNER JOIN inserted i
    ON i.pony_name = t.pony_name
    )
    THROW 911911, 'A pony must have a name as unique as s/he is. --PAS', 16;
 ELSE
    INSERT INTO PONY.tbl_pony (pony_name, stable_id, pet_human_id)
    SELECT pony_name, stable_id, pet_human_id
    FROM inserted
 END

-1

Bạn không thể làm điều này với một UNIQUEràng buộc, nhưng bạn có thể làm điều này trong một kích hoạt.

    CREATE TRIGGER [dbo].[OnInsertMyTableTrigger]
   ON  [dbo].[MyTable]
   INSTEAD OF INSERT
AS 
BEGIN
    SET NOCOUNT ON;

    DECLARE @Column1 INT;
    DECLARE @Column2 INT; -- allow nulls on this column

    SELECT @Column1=Column1, @Column2=Column2 FROM inserted;

    -- Check if an existing record already exists, if not allow the insert.
    IF NOT EXISTS(SELECT * FROM dbo.MyTable WHERE Column1=@Column1 AND Column2=@Column2 @Column2 IS NOT NULL)
    BEGIN
        INSERT INTO dbo.MyTable (Column1, Column2)
            SELECT @Column2, @Column2;
    END
    ELSE
    BEGIN
        RAISERROR('The unique constraint applies on Column1 %d, AND Column2 %d, unless Column2 is NULL.', 16, 1, @Column1, @Column2);
        ROLLBACK TRANSACTION;   
    END

END

-1
CREATE UNIQUE NONCLUSTERED INDEX [UIX_COLUMN_NAME]
ON [dbo].[Employee]([Username] ASC) WHERE ([Username] IS NOT NULL) 
WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, 
DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF, 
MAXDOP = 0) ON [PRIMARY];

-1

mã này nếu bạn tạo một biểu mẫu đăng ký với textBox và sử dụng insert và ur textBox trống và bạn bấm vào nút gửi.

CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.