Boş değerlere de izin veren benzersiz bir kısıtlama nasıl oluştururum?


620

GUID'lerle dolduracağım bir sütun üzerinde benzersiz bir kısıtlama olmasını istiyorum. Ancak verilerim bu sütunlar için boş değerler içeriyor. Birden çok boş değere izin veren kısıtlamayı nasıl oluştururum?

İşte bir örnek senaryo . Bu şemayı düşünün:

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)
)

Sonra ne elde etmek çalışıyorum için bu kodu görmek:

-- 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);

Son ifade bir mesajla başarısız olur:

EŞSİZ ANAHTAR kısıtlaması 'UQ_People_LibraryCardId' ihlali. 'Dbo.People' nesnesine yinelenen anahtar eklenemiyor.

Şema ve / veya teklik kısıtlamamı NULL, gerçek veriler üzerinde benzersizliği kontrol ederken birden fazla değere izin verecek şekilde nasıl değiştirebilirim ?


Oy vermek için standart uyumluluk sorunu bağlayın: connect.microsoft.com/SQLServer/Feedback/Details/299229
Vadzim


EŞSİZ kısıtlama ve NULL'lara izin ver. ? Sağduyu. Mümkün değil
flik

13
@flik, "sağduyu" anlamına gelmeseniz iyi olur. Bu geçerli bir argüman değil. Özellikle bunu düşünürken nullbir değer değil, değer yokluğu. SQL standardına göre, nulleşit kabul edilmez null. Peki neden birden fazla nullteklik ihlali olmalı?
Frédéric

Yanıtlar:


144

SQL Server 2008 +

Bir WHEREcümle ile birden çok NULL kabul eden benzersiz bir dizin oluşturabilirsiniz . Bkz aşağıda cevabını .

SQL Server 2008 öncesi

BENZERSİZ bir kısıtlama oluşturamaz ve NULL'lara izin veremezsiniz. Varsayılan bir NEWID () değeri ayarlamanız gerekir.

UNIQUE kısıtlaması oluşturmadan önce varolan değerleri NEWID () olarak NULL olarak güncelleyin.


2
ve bu retrospektif olarak mevcut satırlara değer katacak, eğer öyleyse yapmam gereken bu, teşekkürler?
Stuart

1
Mevcut değerleri NEWID () olarak ayarlamak için bir UPDATE deyimi çalıştırmanız gerekir; burada mevcut alan NULL'dur
Jose Basilio

54
SQL Server 2008 veya üstünü kullanıyorsanız, 100'den fazla upvotes ile aşağıdaki cevaba bakınız. Benzersiz Kısıtlamanıza bir WHERE yantümcesi ekleyebilirsiniz.
Darren Griffith

1
Bu çok sorun ADO.NET DataTables da vurur. Bu yöntemi kullanarak yedekleme alanındaki null'lara izin verebilsem bile, DataTable NULL'ları ilk başta benzersiz bir sütunda depolamama izin vermiyor. Herkes bunun için bir çözüm biliyorsa, lütfen buraya
dotNET

6
Çocuklar, 600 upvotes ile aşağı kaydırıp okuduğunuzdan emin olurlar. Sadece 100 üzerinden artık en
Luminous

1288

Aradığınız şey aslında ANSI standartları SQL: 92, SQL: 1999 ve SQL: 2003'ün bir parçasıdır, yani UNIQUE kısıtlaması yinelenen NULL olmayan değerlere izin vermemelidir, ancak birden fazla NULL değeri kabul etmelidir.

Ancak SQL Server'ın Microsoft dünyasında, tek bir NULL'a izin verilir, ancak birden fazla NULL'a izin verilmez ...

Gelen SQL Server 2008 , sen hariç tutar boş değerlere bir yüklem dayalı benzersiz bir Filtrelenen endeksi tanımlayabilirsiniz:

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

Önceki sürümlerde, kısıtlamayı zorunlu kılmak için NOT NULL olmayan bir tahminle VIEWS'a başvurabilirsiniz.


3
bu muhtemelen bunu yapmanın en iyi yoludur. herhangi bir performans etkisi olup olmadığından emin değil misiniz? kimse?
Simon_Weaver

3
SQL Server 2008 Express sürümünde tam olarak bunu yapmaya çalışıyorum ve aşağıdaki gibi bir hata alıyorum: [SLS-CP] .dbo.MasterFileEntry (MailingId) NEREDE OLMAYAN INDEX UC_MailingId OLUŞTURMADAN Sonuçlar: Msg 156, Seviye 15, Durum 1, Satır 3 'WHERE' anahtar kelimesinin yanında yanlış sözdizimi. Eğer DDC'nin nerede çalıştığı nerede koşulunu kaldırırsam, ama elbette, ihtiyacım olan şeyi yapmaz. Herhangi bir fikir?
Kenneth Baltrinic

4
Yanılmıyorsam, Benzersiz bir Kısıtlamada olduğu gibi Benzersiz bir Dizin üzerinden Yabancı Anahtar oluşturamazsınız. (En azından SSMS denediğimde bana şikayet etti.) Yabancı Anahtar ilişkisinin kaynağı her zaman benzersiz olan (boş değilse) null olabilecek bir sütuna sahip olmak güzel olurdu.
Vaccano

8
Gerçekten harika bir cevap. Cevap olarak kabul edilen kişi tarafından gizlenmesi çok kötü. Bu çözüm neredeyse dikkatimi çekmedi, ama şimdi benim uygulamada harikalar gibi çalışıyor.
Coral Doe

2
SQL 2005 ve altı için başka bir alternatif de Hesaplanmış Sütun olarak adlandırılan "Nullbuster" hilesidir. stackoverflow.com/a/191729/132461 Veritabanını başka bir görünümle karıştırmaktan kurtarır, bunun yerine başka bir sütununuz vardır - ColumnA ANSI nulllable UNIQUE olmasını istediğiniz sütunsa genellikle ColumnA-Nullbuster olarak adlandırılır. ColumnA-Nullbuster üzerine BENZERSİZ bir Endeks (veya iş amacını ifade etmek için bir kısıtlama) koyun ve A
sütunu

34

SQL Server 2008 ve Üzeri

Yalnızca benzersiz bir dizine filtre uygulayın:

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

Alt Sürümlerde, Gerçekleştirilmiş Bir Görünüm Hala Gerekmez

SQL Server 2005 ve öncesi için, görünüm olmadan yapabilirsiniz. Tablolarımdan birine istediğiniz gibi benzersiz bir kısıtlama ekledim. Sütunda benzersizlik istediğim SamAccountName, ancak birden fazla NULL'a izin vermek istediğim için, materyalize görünüm yerine materyalize bir sütun kullandım:

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

Sadece istenen gerçek sütun NULL olduğunda, hesaplanan sütuna tüm tablo boyunca benzersiz garanti edilecek bir şey koymak zorunda. Bu durumda, PartyIDbir kimlik sütunu ve sayısal olmak hiçbiriyle eşleşmeyecek SamAccountName, bu yüzden benim için çalıştı. Kendi yönteminizi deneyebilirsiniz; gerçek verilerinizle kesişme olasılığı olmaması için verilerinizin etki alanını anladığınızdan emin olun. Bu, bunun gibi farklılaştırıcı bir karakter eklemek kadar basit olabilir:

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

PartyIDBir gün sayısal olmasa ve bir ile çakışsa bile SamAccountName, şimdi önemli olmayacak.

Hesaplanan sütunu içeren bir dizinin varlığının, her bir ifade sonucunun tablodaki diğer verilerle birlikte diske kaydedilmesine neden olduğunu ve bunun da ek disk alanı OLDUĞUNU unutmayın.

Bir dizin istemiyorsanız, anahtar kelimeyi PERSISTEDsütun ifadesi tanımının sonuna ekleyerek ifadenin diske önceden hesaplanmasını sağlayarak CPU'yu kaydedebilirsiniz .

SQL Server 2008 ve sonraki sürümlerde, mümkünse kesinlikle filtrelenmiş çözümü kullanın!

tartışma

Bazı veritabanı uzmanlarının bunu kesinlikle sorunları olan "vekil NULL'lar" olarak göreceğini unutmayın (çoğunlukla bir şeyin gerçek bir değer veya eksik veriler için bir vekil değer olup olmadığını belirlemeye çalışmakla ilgili sorunlar nedeniyle; sorunlar da olabilir) NULL olmayan vekil değerlerin sayısının çılgın gibi çoğalmasıyla).

Ancak, bu davanın farklı olduğuna inanıyorum. Eklediğim hesaplanmış sütun hiçbir şeyi belirlemek için asla kullanılmayacaktır. Kendisinin bir anlamı yoktur ve uygun şekilde tanımlanmış diğer sütunlarda zaten ayrı bulunmayan hiçbir bilgiyi kodlamaz. Asla seçilmemeli veya kullanılmamalıdır.

Öyleyse, benim hikayem bunun bir vekil NULL olmadığı ve buna bağlı kalıyorum! Aslında, NULL olmayan değeri, UNIQUENULL'ları yoksaymak için dizini kandırmaktan başka bir amaç için istemediğimizden, kullanım durumumuzda, normal vekil NULL oluşturmada ortaya çıkan sorunlardan hiçbiri yoktur.

Tüm bunlar, bunun yerine dizinli bir görünüm kullanmayla ilgili bir sorunum yok - ancak kullanım gereksinimi gibi bazı sorunlar getiriyor SCHEMABINDING. Temel tablonuza yeni bir sütun ekleyerek eğlenin (en azından dizini bırakmanız ve ardından görünümü bırakmanız veya görünümü şemaya bağlı olmayacak şekilde değiştirmeniz gerekir). SQL Server'da (2005) (ayrıca sonraki sürümlerde), (2000) dizinli görünüm oluşturmak için gereksinimlerin tam listesine bakın .

Güncelleme

Sütununuz sayısalsa, benzersiz kısıtlamanın kullanımının Coalesceçarpışmalara yol açmamasını sağlama zorluğu olabilir . Bu durumda, bazı seçenekler vardır. Negatif bir sayı kullanmak, "vekil NULL'ları" yalnızca negatif aralığa ve "gerçek değerleri" yalnızca pozitif aralığa koymak olabilir. Alternatif olarak, aşağıdaki desen kullanılabilir. Tabloda Issue( IssueIDolan PRIMARY KEY) veya orada olabilir veya olmayabilir TicketID, ancak biri varsa, o benzersiz olmalıdır.

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);

Sorun 1 1 bilet 123 varsa, UNIQUEkısıt değerleri (123, NULL) olacaktır. IssueID 2'de bilet yoksa açık olacaktır (NULL, 2). Bazı düşünceler, bu kısıtlamanın tablodaki herhangi bir satır için çoğaltılamayacağını ve yine de birden çok NULL'a izin vereceğini gösterecektir.


16

Microsoft SQL Server Manager'ı kullanan ve Benzersiz ama Null olabilecek bir dizin oluşturmak isteyen kişiler için normalde yeni dizininiz için Dizin Özelliklerinizde yaptığınız gibi benzersiz dizininizi oluşturabilir, sol panelden "Filtre" yi seçip filtreniz (bu sizin nerede olduğunuzdur). Bunun gibi bir şey okumalıdır:

([YourColumnName] IS NOT NULL)

Bu MSSQL 2012 ile çalışır


: Microsoft SQL Server Management Studio altında filtrelenmiş endeks Burada anlatılan ve mükemmel çalışıyor olmaması için ne msdn.microsoft.com/en-us/library/cc280372.aspx
Jan

9

Aşağıdaki benzersiz dizini uyguladığımda:

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

null olmayan her güncelleme ve ekleme aşağıdaki hatayla başarısız oldu:

Aşağıdaki SET seçeneklerinin yanlış ayarları olduğundan UPDATE başarısız oldu: 'ARITHABORT'.

Bunu MSDN'de buldum

Hesaplanan sütunlarda veya dizinlenmiş görünümlerde dizin oluştururken veya değiştirirken ARITHABORT AYARLARININ AÇIK olması gerekir. SET ARITHABORT KAPALI ise, hesaplanan sütunlarda veya dizinlenmiş görünümlerde dizinleri olan tablolarda CREATE, UPDATE, INSERT ve DELETE deyimleri başarısız olur.

Bu yüzden düzgün çalışması için bunu yaptım

[Veritabanı] -> Özellikler -> Seçenekler -> Diğer Seçenekler -> Çeşitli -> Aritmetik Durdur Etkin -> true

Kod kullanarak bu seçeneği ayarlamak mümkün olduğuna inanıyorum

ALTER DATABASE "DBNAME" SET ARITHABORT ON

ama bunu test etmedim


6

Yalnızca NULLsütun olmayanları seçen bir görünüm oluşturun ve görünümü UNIQUE INDEXüzerinde oluşturun :

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

CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn)

Gerçekleştirmek gerekeceğini Not INSERT'ler ve UPDATEyerine tablonun görünümü' ın.

Bir INSTEAD OFtetikleyici ile yapabilirsiniz :

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

görünüme eklemek için dalımı değiştirmem gerekiyor mu?
Stuart

1
Tetikleyici INSTEAD OF INSERT oluşturabilirsiniz.
Quassnoi

6

Tasarımcıda da yapılabilir

Bu pencereyi almak için Dizin> Özellikler'e sağ tıklayın

ele geçirmek


Tasarımcıya erişiminiz varsa çok güzel bir alternatif
Francisco

Daha önce keşfettiğim gibi, tablonuzda veri bulunduğunda, artık tasarımcıyı kullanamazsınız. Filtreyi yok sayıyor gibi görünüyor ve denenen tablo güncellemeleri "Yinelenen anahtara izin verilmiyor" mesajı ile karşılanıyor
MortimerCat

4

Kümelenmiş Dizine Alınmış Görünümde benzersiz bir kısıtlama oluşturmak mümkündür

Görünümü şu şekilde oluşturabilirsiniz:

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

ve bunun gibi benzersiz kısıtlama:

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

2

Belki " INSTEAD OF" tetikleyicisini düşünün ve kontrolü kendiniz yapın? Aramayı etkinleştirmek için sütunda kümelenmemiş (benzersiz olmayan) bir dizin bulunur.


1

Daha önce belirtildiği gibi, SQL Server söz konusu olduğunda ANSI standardını uygulamamaktadır UNIQUE CONSTRAINT. Microsoft Connect'te 2007'den beri bunun için bir bilet var . Burada ve burada önerildiği gibi bugün itibariyle en iyi seçenekler, başka bir cevapta veya hesaplanmış bir sütunda belirtildiği gibi filtrelenmiş bir dizin kullanmaktır , örneğin:

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

Belirli koşulların ve karşılanmaları durumunda hatayı kontrol etmek için bir INSTEAD OF tetikleyicisi oluşturabilirsiniz . Dizin oluşturmak daha büyük tablolarda maliyetli olabilir.

İşte bir örnek:

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

Bunu bir UNIQUEkısıtlamayla yapamazsınız, ancak bunu bir tetikleyicide yapabilirsiniz.

    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

u textBox ile bir kayıt formu yapmak ve insert ve ur textBox kullanırsanız bu kod boş ve u gönder butonuna tıklayın.

CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.