Як створити унікальне обмеження, яке також дозволяє нулі?


620

Я хочу мати унікальне обмеження на стовпчик, який я збираюсь заповнити GUID. Однак мої дані містять нульові значення для цих стовпців. Як створити обмеження, яке дозволяє кілька нульових значень?

Ось приклад сценарію . Розглянемо цю схему:

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

Потім перегляньте цей код того, чого я намагаюся досягти:

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

Остаточне твердження не вдається з повідомленням:

Порушення обмеження UNIQUE KEY 'UQ_People_LibraryCardId'. Неможливо вставити повторюваний ключ в об’єкт 'dbo.People'.

Як я можу змінити схему та / або обмеження унікальності, щоб вона дозволяла мати декілька NULLзначень, при цьому ще перевіряючи унікальність фактичних даних?


Проблема підключення для стандартної сумісності для голосування за: connect.microsoft.com/SQLServer/Feedback/Details/299229
Вадим


УНІКАЛЬНЕ обмеження та дозволити NULL. ? Це здоровий глузд. Це неможливо
flik

13
@flik, краще не посилайтеся на "здоровий глузд". Це неправдивий аргумент. Особливо якщо врахувати, що nullце не цінність, а відсутність цінності. Згідно стандарту SQL, nullне вважається рівним null. То чому множинність nullмає бути порушенням унікальності?
Фредерік

Відповіді:


144

SQL Server 2008 +

Ви можете створити унікальний індекс, який приймає декілька NULL з WHEREпропозицією. Дивіться відповідь нижче .

До початку SQL Server 2008

Ви не можете створити UNIQUE обмеження та дозволити NULL. Вам потрібно встановити значення за замовчуванням NEWID ().

Оновіть існуючі значення на NEWID (), де NULL, перш ніж створити UNIQUE обмеження.


2
і це ретроспективно додасть значення існуючим рядкам, якщо це так, що мені потрібно зробити, дякую?
Стюарт

1
Вам потрібно запустити операцію UPDATE, щоб встановити існуючі значення в NEWID (), де існуюче поле НУЛЬ
Jose Basilio

54
Якщо ви використовуєте SQL Server 2008 або новішої версії, дивіться відповідь нижче з понад 100 оновленими повідомленнями. Ви можете додати пункт WHERE до свого унікального обмеження.
Даррен Гріффіт

1
Ця проблема також стосується ADO.NET DataTables. Тож навіть якщо я можу дозволити нулі в області резервного копіювання за допомогою цього методу, DataTable не дозволить мені зберігати NULL в першу чергу унікальному стовпчику. Якщо хтось знає рішення для цього, будь ласка, опублікуйте його тут
dotNET

6
Хлопці переконайтеся, що ви прокручуєте вниз і читаєте відповідь з 600 посилань. Це вже не більше 100.
Світлий

1287

Те, що ви шукаєте, насправді є частиною стандартів ANSI SQL: 92, SQL: 1999 та SQL: 2003, тобто обмеження UNIQUE повинно забороняти дублювати значення, що не мають NULL, але приймати кілька значень NULL.

Однак у світі Microsoft SQL Server допускається одна NULL, але декілька NULL не є ...

У SQL Server 2008 ви можете визначити унікальний відфільтрований індекс на основі предиката, який виключає NULL:

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

У попередніх версіях ви можете вдатися до VIEWS з предикатом NOT NULL для забезпечення обмеження.


3
це, мабуть, найкращий спосіб зробити це. не впевнений, чи є якісь наслідки від продуктивності? хтось?
Simon_Weaver

3
Я намагаюся зробити саме це у виданні SQL Server 2008 Express, і я отримую помилку наступним чином: СТВОРИТЬ УНІКАЛЬНУ НЕЗАКОНУВАННУ ІНДЕКСУ UC_MailingId ON [SLS-CP] .dbo.MasterFileEntry (MailingId), де MailingId НЕ NULL Результати в: Msg 156, Рівень 15, стан 1, рядок 3 Неправильний синтаксис біля ключового слова "ДЕ". Якщо я видаляю пункт де, DDL працює нормально, але, звичайно, не робить те, що мені потрібно. Будь-які ідеї?
Кеннет Балтрінік

4
Якщо я не помиляюся, ви не можете створити зовнішній ключ з унікального індексу, як ви можете вимкнути унікальний обмеження. (Принаймні, SSMS скаржився на мене, коли я намагався.) Було б непогано мати змогу мати нульовий стовпчик, який завжди унікальний (коли не нульовий) є джерелом відносин із зовнішнім ключем.
Ваккано

8
Воістину чудова відповідь. Шкода, що його приховав той, кого прийняли за відповідь. Це рішення майже не припало мені до уваги, але воно працює як чудеса в моїй реалізації зараз.
Coral Doe

2
Ще одна альтернатива для SQL 2005 та нижче - обчислювальна колонка, яка називається "Nullbuster". stackoverflow.com/a/191729/132461 Це рятує вас від захаращення бази даних з іншим видом, у вас просто є інший стовпець - зазвичай називається ColumnA-Nullbuster, якщо ColumnA - це той, який ви хочете бути ANSI нульовим UNIQUE. Покладіть UNIQUE Index (або обмеження на висловлення ділових намірів) на ColumnA-Nullbuster, і він набуде унікальності на ColumnA
DanO

34

SQL Server 2008 і вище

Просто відфільтруйте унікальний індекс:

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

У нижчих версіях матеріалізований вигляд все ще не потрібен

Для SQL Server 2005 та новіших версій ви можете це робити без перегляду. Я щойно додав унікальне обмеження, як ви просите, до однієї з моїх таблиць. Враховуючи, що я хочу унікальності в стовпці SamAccountName, але я хочу дозволити кілька NULL, я використовував матеріалізований стовпець, а не матеріалізований вигляд:

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

Вам просто потрібно помістити в обчислений стовпець щось, що буде гарантовано унікальним у всій таблиці, коли фактичний потрібний унікальний стовпець буде NULL. У цьому випадку PartyIDстовпчик ідентичності є чисельним числом і ніколи не відповідає жодному SamAccountName, тому він працював для мене. Ви можете спробувати власний метод - будьте впевнені, що ви розумієте домен своїх даних, щоб не було можливості перетину реальних даних. Це може бути таким же простим, як попередження подібного символу диференціатора:

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

Навіть якщо PartyIDколись вона стала нечисловою і могла збігатися з a SamAccountName, тепер це не має значення.

Зауважте, що наявність індексу, що включає обчислений стовпець, неявно призводить до збереження кожного результату вираження на диску з іншими даними таблиці, які НЕ займають додаткового місця на диску.

Зауважте, що якщо ви не хочете індексу, ви все одно можете зберегти ЦП, зробивши вираз попередньо обчислений на диску, додавши ключове слово PERSISTEDдо кінця визначення виразу стовпця.

У SQL Server 2008 і новіших версіях обов'язково використовуйте відфільтрований розчин замість цього, якщо можливо.

Суперечки

Зверніть увагу, що деякі фахівці з баз даних сприйматимуть це як випадок "сурогатних NULL", які, безумовно, мають проблеми (в основному через проблеми навколо спроби визначити, коли щось є реальним значенням або сурогатним значенням для відсутніх даних ; також можуть виникнути проблеми з числом сурогатних значень, що не мають NULL, множиться як божевільні).

Однак я вважаю, що ця справа інша. Обчислюваний стовпець, який я додаю, ніколи не буде використовуватися для визначення нічого. Він не має сенсу сам по собі і не кодує ніякої інформації, яка вже не зустрічається окремо в інших, правильно визначених стовпцях. Його ніколи не слід вибирати чи використовувати.

Отже, моя історія полягає в тому, що це не сурогатний NULL, і я дотримуюся цього! Оскільки ми насправді не хочемо значення NULL з будь-якою метою, крім того, щоб обдурити UNIQUEіндекс, щоб ігнорувати NULL, у нашому випадку використання немає жодної проблеми, що виникає при звичайному сурогатному створенні NULL.

Все, що було сказано, я не маю жодних проблем із використанням індексованого виду, але це викликає деякі проблеми, такі як вимога використання SCHEMABINDING. Приємно додайте новий стовпець до базової таблиці (вам потрібно як мінімум скинути індекс, а потім випустити подання або змінити вигляд, щоб він не був прив’язаний до схеми). Дивіться повний (довгий) список вимог до створення індексованого виду в SQL Server (2005) (також пізніші версії), (2000) .

Оновлення

Якщо ваш стовпець чисельний, може виникнути завдання забезпечити, щоб використання унікального обмеження Coalesceне призвело до зіткнень. У цьому випадку є деякі варіанти. Можливо, використовувати негативне число, ставити "сурогатні NULL" лише в негативний діапазон, а "реальні значення" лише в позитивному діапазоні. Як варіант, може бути використаний наступний зразок. У таблиці Issue(де IssueIDзнаходиться PRIMARY KEY) може бути або не бути TicketID, але якщо така є, вона повинна бути унікальною.

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

Якщо у IssueID 1 є квиток 123, UNIQUEобмеження буде на значеннях (123, NULL). Якщо у IssueID 2 немає квитка, він увімкнеться (NULL, 2). Деякі думки покажуть, що це обмеження не може бути дубльоване для жодного рядка таблиці, і все ж дозволяє кілька NULL.


16

Для людей, які використовують Microsoft SQL Server Manager і хочуть створити унікальний, але інтуїтивний індекс, ви можете створити свій унікальний індекс так, як зазвичай це було б у Ваших індексних властивостях для нового індексу, виберіть "Фільтр" на панелі ліворуч та введіть ваш фільтр (який є вашим пунктом де). Він повинен читати щось подібне:

([YourColumnName] IS NOT NULL)

Це працює з MSSQL 2012


Як зробити фільтрований індекс під Microsoft SQL Server Management Studio описано тут і прекрасно працює: msdn.microsoft.com/en-us/library/cc280372.aspx
Jan

9

Коли я застосував унікальний індекс нижче:

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

невдале оновлення та вставлення не вдалося з помилкою нижче:

Оновлення не вдалося, оскільки в наступних параметрах SET є невірні параметри: 'ARITHABORT'.

Я знайшов це на MSDN

SET ARITHABORT повинен бути увімкнено, коли ви створюєте або змінюєте індекси на обчислених стовпцях або індексованих представленнях. Якщо SET ARITHABORT вимкнено, CREATE, UPDATE, INSERT і DELETE на таблицях з індексами обчислених стовпців або індексованими представленнями не вдасться.

Тож, щоб це правильно працювало, я це зробив

Клацніть правою кнопкою миші [База даних] -> Властивості -> Опції -> Інші параметри -> Різне -> Арифметичний перерив увімкнено -> вірно

Я вважаю, що встановити цю опцію в коді можна

ALTER DATABASE "DBNAME" SET ARITHABORT ON

але я цього не перевіряв


6

Створіть подання, яке вибирає лише не NULLстовпці, і створіть UNIQUE INDEXу перегляді:

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

CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn)

Зауважте, що вам потрібно буде виконати INSERT'і і UPDATE' на поданні замість таблиці.

Ви можете зробити це за допомогою INSTEAD OFтригера:

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

тож мені потрібно змінити дал, щоб вставити його у подання?
Стюарт

1
Ви можете створити тригер INSTEAD OF INSERT.
Quassnoi

6

Це можна зробити і в дизайнера

Клацніть правою кнопкою миші на індекс> Властивості, щоб отримати це вікно

захоплення


Дуже приємна альтернатива, якщо у вас є доступ до дизайнера
Франциско

Хоча, як я нещодавно з’ясував, щойно ви матимете дані у своїй таблиці, ви більше не можете використовувати конструктор. Здається, ігнорують фільтр, і будь-які спроби оновлення таблиці зустрічаються з повідомленням "Дублікат ключа не дозволений"
MortimerCat

4

Можна створити унікальне обмеження для кластеризованого індексованого виду

Ви можете створити Вигляд таким чином:

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

і унікальне обмеження, як це:

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

2

Може, розглянути " INSTEAD OF" тригер і зробити перевірку самостійно? З некластеризованим (не унікальним) індексом на стовпці для активації пошуку.


1

Як було сказано раніше, SQL Server не реалізує стандарт ANSI, коли це стосується UNIQUE CONSTRAINT. На Microsoft Connect існує квиток на це з 2007 року. Як запропоновано там і тут, найкращі варіанти на сьогоднішній день - використовувати відфільтрований індекс, як зазначено в іншій відповіді або обчисленому стовпчику, наприклад:

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

Ви можете створити тригер INSTEAD OF, щоб перевірити наявність конкретних умов та помилок, якщо вони виконані. Створення індексу може бути дорогим для великих таблиць.

Ось приклад:

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

Ви не можете зробити це з UNIQUEобмеженням, але ви можете зробити це за допомогою тригера.

    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

цей код, якщо ви робите реєстраційну форму з textBox і використовуєте вставку та ур textBox порожній, а ви натискаєте кнопку подати.

CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.