Wie erstelle ich eine eindeutige Einschränkung, die auch Nullen zulässt?

620

Ich möchte eine eindeutige Einschränkung für eine Spalte haben, die ich mit GUIDs füllen werde. Meine Daten enthalten jedoch Nullwerte für diese Spalten. Wie erstelle ich die Einschränkung, die mehrere Nullwerte zulässt?

Hier ist ein Beispielszenario . Betrachten Sie dieses Schema:

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

Dann sehen Sie diesen Code für das, was ich erreichen möchte:

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

Die endgültige Anweisung schlägt mit einer Meldung fehl:

Verletzung der UNIQUE KEY-Einschränkung 'UQ_People_LibraryCardId'. Doppelter Schlüssel kann nicht in Objekt 'dbo.People' eingefügt werden.

Wie kann ich mein Schema und / oder meine Eindeutigkeitsbeschränkung so ändern, dass mehrere NULLWerte zulässig sind, während die Eindeutigkeit der tatsächlichen Daten überprüft wird?

Stuart
quelle
Verbindungsproblem für Standardkompatibilität zur Abstimmung: connect.microsoft.com/SQLServer/Feedback/Details/299229
Vadzim
EINZIGARTIGE Einschränkung und Zulassen von NULL-Werten. ? Es ist gesunder Menschenverstand. Es ist nicht möglich
Flik
13
@flik, besser nicht auf "gesunden Menschenverstand" beziehen. Das ist kein gültiges Argument. Besonders wenn man bedenkt, dass dies nullkein Wert ist, sondern das Fehlen von Wert. Wird nach dem SQL-Standard nullnicht als gleich angesehen null. Warum nullsollte Multiple also eine Verletzung der Eindeutigkeit sein?
Frédéric

Antworten:

144

SQL Server 2008 +

Sie können einen eindeutigen Index erstellen, der mehrere NULL-Werte mit einer WHEREKlausel akzeptiert . Siehe die Antwort unten .

Vor SQL Server 2008

Sie können keine EINZIGARTIGE Einschränkung erstellen und NULL-Werte zulassen. Sie müssen den Standardwert NEWID () festlegen.

Aktualisieren Sie die vorhandenen Werte auf NEWID (), wobei NULL ist, bevor Sie die UNIQUE-Einschränkung erstellen.

Jose Basilio
quelle
2
und dies wird nachträglich Werte zu vorhandenen Zeilen hinzufügen. Wenn ja, muss ich dies tun, danke?
Stuart
1
Sie müssten eine UPDATE-Anweisung ausführen, um die vorhandenen Werte auf NEWID () zu setzen, wobei das vorhandene Feld NULL ist
Jose Basilio
54
Wenn Sie SQL Server 2008 oder höher verwenden, lesen Sie die Antwort unten mit über 100 Upvotes. Sie können Ihrer eindeutigen Einschränkung eine WHERE-Klausel hinzufügen.
Darren Griffith
1
Dieses Problem betrifft auch ADO.NET DataTables. Selbst wenn ich mit dieser Methode Nullen im Hintergrundfeld zulassen kann, kann ich in der DataTable NULL-Werte nicht in einer eindeutigen Spalte speichern. Wenn jemand eine Lösung dafür kennt,
poste
6
Jungs stellen sicher, dass Sie nach unten scrollen und die Antwort mit 600 positiven Stimmen lesen. Es ist nicht mehr knapp über 100.
Luminous
1288

Was Sie suchen, ist in der Tat Teil der ANSI-Standards SQL: 92, SQL: 1999 und SQL: 2003, dh eine UNIQUE-Einschränkung muss doppelte Nicht-NULL-Werte nicht zulassen, aber mehrere NULL-Werte akzeptieren.

In der Microsoft-Welt von SQL Server ist jedoch ein einzelner NULL-Wert zulässig, mehrere NULL-Werte jedoch nicht ...

In SQL Server 2008 können Sie einen eindeutigen gefilterten Index basierend auf einem Prädikat definieren, das NULL-Werte ausschließt:

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

In früheren Versionen können Sie mit einem NOT NULL-Prädikat auf VIEWS zurückgreifen, um die Einschränkung zu erzwingen.

Vincent Buck
quelle
3
Dies ist wahrscheinlich der beste Weg, dies zu tun. Sie sind sich nicht sicher, ob es Leistungseinbußen gibt? jemand?
Simon_Weaver
3
Ich versuche genau dies in SQL Server 2008 Express Edition zu tun und erhalte folgende Fehlermeldung: CREATE UNIQUE NONCLUSTERED INDEX UC_MailingId ON [SLS-CP] .dbo.MasterFileEntry (MailingId) WHERE MailingId IST NICHT NULL Ergebnisse in: Msg 156, Level 15, Status 1, Zeile 3 Falsche Syntax in der Nähe des Schlüsselworts 'WHERE'. Wenn ich die where-Klausel entferne, läuft die DDL einwandfrei, tut aber natürlich nicht das, was ich brauche. Irgendwelche Ideen?
Kenneth Baltrinic
4
Wenn ich mich nicht irre, können Sie keinen Fremdschlüssel aus einem eindeutigen Index erstellen, wie Sie es aus einer eindeutigen Einschränkung heraus tun können. (Zumindest SSMS hat sich bei mir beschwert, als ich es versuchte.) Es wäre schön, wenn eine nullbare Spalte, die immer eindeutig ist (wenn nicht null), die Quelle einer Fremdschlüsselbeziehung wäre.
Vaccano
8
Wirklich eine gute Antwort. Schade, dass es von dem versteckt wurde, der als Antwort akzeptiert wurde. Diese Lösung ist mir fast nicht aufgefallen, aber sie wirkt jetzt wie Wunder in meiner Implementierung.
Coral Doe
2
Eine weitere Alternative für SQL 2005 und niedriger ist ein Trick mit berechneten Spalten, auch bekannt als "Nullbuster" -Trick. stackoverflow.com/a/191729/132461 Es erspart Ihnen, die Datenbank mit einer anderen Ansicht zu überladen. Stattdessen haben Sie nur eine andere Spalte - normalerweise ColumnA-Nullbuster genannt, wenn ColumnA diejenige ist, die ANSI nullable UNIQUE sein soll. Setzen Sie einen EINZIGARTIGEN Index (oder eine Einschränkung, um Geschäftsabsichten auszudrücken) auf ColumnA-Nullbuster und erzwingt die Eindeutigkeit von ColumnA
DanO
34

SQL Server 2008 und höher

Filtern Sie einfach einen eindeutigen Index:

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

In niedrigeren Versionen ist eine materialisierte Ansicht immer noch nicht erforderlich

Für SQL Server 2005 und früher können Sie dies ohne Ansicht tun. Ich habe gerade einer meiner Tabellen eine eindeutige Einschränkung hinzugefügt, nach der Sie fragen. Da ich die Eindeutigkeit in der Spalte möchte SamAccountName, aber mehrere NULL-Werte zulassen möchte, habe ich eine materialisierte Spalte anstelle einer materialisierten Ansicht verwendet:

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

Sie müssen lediglich etwas in die berechnete Spalte einfügen, das in der gesamten Tabelle garantiert eindeutig ist, wenn die tatsächlich gewünschte eindeutige Spalte NULL ist. In diesem Fall PartyIDhandelt es sich um eine Identitätsspalte, und numerisch zu sein wird niemals mit einer übereinstimmen. Daher SamAccountNamehat es bei mir funktioniert. Sie können Ihre eigene Methode ausprobieren - stellen Sie sicher, dass Sie die Domäne Ihrer Daten verstehen, damit keine Möglichkeit besteht, sich mit realen Daten zu überschneiden. Das könnte so einfach sein, als würde man ein Unterscheidungsmerkmal wie dieses voranstellen:

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

Selbst wenn es PartyIDeines Tages nicht numerisch wurde und mit einem zusammenfallen könnte SamAccountName, spielt es jetzt keine Rolle mehr.

Beachten Sie, dass das Vorhandensein eines Index einschließlich der berechneten Spalte implizit dazu führt, dass jedes Ausdrucksergebnis mit den anderen Daten in der Tabelle auf der Festplatte gespeichert wird, was zusätzlichen Speicherplatz beansprucht.

Beachten Sie, dass Sie, wenn Sie keinen Index möchten, dennoch CPU sparen können, indem Sie den Ausdruck auf der Festplatte vorberechnen, indem Sie das Schlüsselwort PERSISTEDam Ende der Definition des Spaltenausdrucks hinzufügen .

Verwenden Sie in SQL Server 2008 und höher auf jeden Fall stattdessen die gefilterte Lösung, wenn Sie können!

Kontroverse

Bitte beachten Sie, dass einige Datenbankprofis dies als einen Fall von "Ersatz-NULL-Werten" betrachten, die definitiv Probleme haben (hauptsächlich aufgrund von Problemen beim Versuch, festzustellen, ob etwas ein realer Wert oder ein Wert ist Ersatzwert für fehlende Daten ist ; es kann auch Probleme geben mit der Anzahl der Nicht-NULL-Ersatzwerte, die sich wie verrückt multiplizieren).

Ich glaube jedoch, dass dieser Fall anders ist. Die berechnete Spalte, die ich hinzufüge, wird niemals verwendet, um etwas zu bestimmen. Es hat keine eigene Bedeutung und codiert keine Informationen, die nicht bereits separat in anderen, ordnungsgemäß definierten Spalten gefunden wurden. Es sollte niemals ausgewählt oder verwendet werden.

Meine Geschichte ist also, dass dies kein Ersatz-NULL ist, und ich bleibe dabei! Da wir den Nicht-NULL-Wert nicht für einen anderen Zweck als zum Trick des UNIQUEIndex zum Ignorieren von NULL-Werten verwenden möchten, weist unser Anwendungsfall keines der Probleme auf, die bei der normalen NULL-Ersatzerstellung auftreten.

Trotzdem habe ich kein Problem damit, stattdessen eine indizierte Ansicht zu verwenden - aber es bringt einige Probleme mit sich, wie z. B. die Anforderung der Verwendung SCHEMABINDING. Viel Spaß beim Hinzufügen einer neuen Spalte zu Ihrer Basistabelle (Sie müssen mindestens den Index löschen und dann die Ansicht löschen oder die Ansicht so ändern, dass sie nicht an ein Schema gebunden ist). Siehe die vollständige (lange) Liste der Anforderungen zum Erstellen einer indizierten Ansicht in SQL Server (2005) (auch spätere Versionen), (2000) .

Aktualisieren

Wenn Ihre Spalte numerisch ist, besteht möglicherweise die Herausforderung, sicherzustellen, dass die Verwendung der eindeutigen Einschränkung Coalescenicht zu Kollisionen führt. In diesem Fall gibt es einige Optionen. Eine könnte darin bestehen, eine negative Zahl zu verwenden, um die "Ersatz-NULL" nur in den negativen Bereich und die "realen Werte" nur in den positiven Bereich zu setzen. Alternativ könnte das folgende Muster verwendet werden. In der Tabelle Issue(wo IssueIDist die PRIMARY KEY) kann es eine geben oder nicht TicketID, aber wenn es eine gibt, muss sie eindeutig sein.

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

Wenn IssueID 1 über Ticket 123 verfügt, gilt die UNIQUEEinschränkung für Werte (123, NULL). Wenn IssueID 2 kein Ticket hat, ist es eingeschaltet (NULL, 2). Einige Überlegungen werden zeigen, dass diese Einschränkung für keine Zeile in der Tabelle dupliziert werden kann und dennoch mehrere NULL-Werte zulässt.

ErikE
quelle
16

Für Benutzer , die Microsoft SQL Server Manager verwenden und einen eindeutigen, aber nullfähigen Index erstellen möchten, können Sie Ihren eindeutigen Index wie gewohnt in Ihren Indexeigenschaften für Ihren neuen Index erstellen. Wählen Sie im linken Bereich "Filter" aus und geben Sie ein Ihr Filter (das ist Ihre where-Klausel). Es sollte ungefähr so ​​lauten:

([YourColumnName] IS NOT NULL)

Dies funktioniert mit MSSQL 2012

Howard
quelle
Wie ein gefilterter Index unter Microsoft SQL Server Management Studio zu machen , wird hier beschrieben und funktioniert perfekt: msdn.microsoft.com/en-us/library/cc280372.aspx
Jan
9

Als ich den folgenden eindeutigen Index angewendet habe:

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

Jedes Nicht-Null-Update und Einfügen schlug mit dem folgenden Fehler fehl:

UPDATE ist fehlgeschlagen, da die folgenden SET-Optionen falsche Einstellungen haben: 'ARITHABORT'.

Ich habe das auf MSDN gefunden

SET ARITHABORT muss aktiviert sein, wenn Sie Indizes für berechnete Spalten oder indizierte Ansichten erstellen oder ändern. Wenn SET ARITHABORT auf OFF gesetzt ist, schlagen die Anweisungen CREATE, UPDATE, INSERT und DELETE für Tabellen mit Indizes für berechnete Spalten oder indizierte Ansichten fehl.

Damit dies richtig funktioniert, habe ich das getan

Klicken Sie mit der rechten Maustaste auf [Datenbank] -> Eigenschaften -> Optionen -> Andere Optionen -> Verschiedenes -> Arithmetischer Abbruch aktiviert -> true

Ich glaube, es ist möglich, diese Option im Code mit zu setzen

ALTER DATABASE "DBNAME" SET ARITHABORT ON

aber ich habe das nicht getestet

Mike Taylor
quelle
6

Erstellen Sie eine Ansicht, in der nur Nicht- NULLSpalten ausgewählt werden, und erstellen Sie Folgendes UNIQUE INDEXin der Ansicht:

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

CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn)

Beachten Sie, dass Sie anstelle der Tabelle INSERT'und UPDATE' in der Ansicht ausführen müssen .

Sie können es mit einem INSTEAD OFAuslöser tun :

CREATE TRIGGER trg_mytable_insert ON mytable
INSTEAD OF INSERT
AS
BEGIN
        INSERT
        INTO    myview
        SELECT  *
        FROM    inserted
END
Quassnoi
quelle
Muss ich mein Dal ändern, um es in die Ansicht einzufügen?
Stuart
1
Sie können einen Trigger STATT EINFÜGEN erstellen.
Quassnoi
6

Dies kann auch im Designer erfolgen

Klicken Sie mit der rechten Maustaste auf Index> Eigenschaften , um dieses Fenster aufzurufen

Erfassung

Yonatan Tuchinsky
quelle
Sehr schöne Alternative, wenn Sie Zugang zum Designer haben
Francisco
Wie ich gerade festgestellt habe, können Sie den Designer nicht mehr verwenden, sobald Sie Daten in Ihrer Tabelle haben. Es scheint den Filter zu ignorieren und alle versuchten Tabellenaktualisierungen werden mit der Meldung "Doppelter Schlüssel nicht erlaubt"
MortimerCat
4

Es ist möglich, eine eindeutige Einschränkung für eine gruppierte indizierte Ansicht zu erstellen

Sie können die Ansicht folgendermaßen erstellen:

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

und die einzigartige Einschränkung wie folgt:

CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE 
  ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues)
Lieven Keersmaekers
quelle
2

Vielleicht einen " INSTEAD OF" Auslöser in Betracht ziehen und die Prüfung selbst durchführen? Mit einem nicht gruppierten (nicht eindeutigen) Index für die Spalte, um die Suche zu ermöglichen.

Marc Gravell
quelle
1

Wie bereits erwähnt, implementiert SQL Server den ANSI-Standard nicht, wenn es darum geht UNIQUE CONSTRAINT. Dafür gibt es seit 2007 ein Ticket für Microsoft Connect . Wie dort und hier vorgeschlagen, besteht die beste Option ab heute darin, einen gefilterten Index zu verwenden, wie in einer anderen Antwort oder einer berechneten Spalte angegeben, z.

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])
)
Baris Akar
quelle
1

Sie können STATT erstellen Trigger , um nach bestimmten Bedingungen und Fehlern zu suchen, wenn diese erfüllt sind. Das Erstellen eines Index kann für größere Tabellen kostspielig sein.

Hier ist ein Beispiel:

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
Paul
quelle
-1

Sie können dies nicht mit einer UNIQUEEinschränkung tun , aber Sie können dies in einem Trigger tun.

    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
Michael Brown
quelle
-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];
user5536124
quelle
-1

Dieser Code, wenn Sie ein Registerformular mit textBox erstellen und einfügen verwenden und Ihre textBox leer ist und Sie auf die Schaltfläche "Senden" klicken.

CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;
Ahmed Soliman Flasha
quelle