SQL я могу иметь «условно уникальное» ограничение для таблицы? - PullRequest
10 голосов
/ 19 июля 2010

Мне приходилось сталкиваться с этим пару раз в моей карьере, и никто из моих местных коллег, кажется, не может ответить на этот вопрос.Скажем, у меня есть таблица с полем «Описание», которое является ключом-кандидатом, за исключением того, что иногда пользователь останавливается в середине процесса.Таким образом, для 25% записей это значение равно нулю, но для всех, которые не равны NULL, оно должно быть уникальным.

Другим примером может быть таблица, которая должна поддерживать несколько «версий» записи, а значение бита указывает, какая из них является «активной».Таким образом, «ключ-кандидат» всегда заполняется, но может быть три идентичные версии (с 0 в активном бите) и только одна, которая активна (1 в активном бите).

У меня есть альтернативаметоды решения этих проблем (в первом случае принудительно применяйте код правила либо в хранимой процедуре, либо на бизнес-уровне, а во втором заполняйте архивную таблицу триггером и объединяйте таблицы, когда мне нужна история).Я не хочу альтернатив (если нет явно лучших решений), мне просто интересно, может ли какой-либо вариант SQL выразить «условную уникальность» таким образом.Я использую MS SQL, так что, если есть способ сделать это, отлично.В основном я просто заинтересован в проблеме.

Ответы [ 5 ]

30 голосов
/ 19 июля 2010

Если вы используете SQL Server 2008, фильтром индекса может быть ваше решение:

http://msdn.microsoft.com/en-us/library/ms188783.aspx

Вот как я применяю уникальный индекс с несколькими значениями NULL

CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL
2 голосов
/ 19 июля 2010

В случае описаний, которые еще не завершены, я не буду располагать их в той же таблице, что и окончательные описания. В итоговой таблице в описании будет указан уникальный индекс или первичный ключ.

В случае активного / неактивного, опять же, я мог бы иметь отдельные таблицы, как вы делали с таблицей «архив» или «история», но другой возможный способ сделать это в MS SQL Server, по крайней мере, через использование индексированное представление:

CREATE TABLE Test_Conditionally_Unique
(
    my_id   INT NOT NULL,
    active  BIT NOT NULL DEFAULT 0
)
GO
CREATE VIEW dbo.Test_Conditionally_Unique_View
WITH SCHEMABINDING
AS
    SELECT
        my_id
    FROM
        dbo.Test_Conditionally_Unique
    WHERE
        active = 1
GO
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id)
GO

INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)    -- This insert will fail

Этот же метод можно использовать и для описаний NULL / Valued.

1 голос
/ 19 июля 2010

Oracle делает.Полностью нулевой ключ не индексируется деревом B в индексе в Oracle, и Oracle использует индексы дерева B для применения уникальных ограничений.

Предполагается, что кто-то желает версии ID_COLUMN на основе того, что ACTIVE_FLAG являетсяустановить 1:

CREATE UNIQUE INDEX idx_versioning_id ON mytable 
  (CASE active_flag WHEN 0 THEN NULL ELSE active_flag END,
   CASE active_flag WHEN 0 THEN NULL ELSE id_column   END);
0 голосов
/ 19 июля 2010

Я не совсем осведомлен о вашем предполагаемом использовании или ваших таблицах, но вы можете попробовать использовать отношения один к одному.Выделите этот «иногда» уникальный столбец в новую таблицу, создайте индекс UNIQUE для этого столбца в новой таблице и верните FK в исходную таблицу, используя исходные таблицы PK.Только в этой новой таблице есть строка, если предполагается, что «уникальные» данные существуют.

СТАРЫЕ таблицы:

TableA
ID    pk
Col1  sometimes unique
Col...

НОВЫЕ таблицы:

TableA
ID
Col...

TableB
ID   PK, FK to TableA.ID
Col1 unique index
0 голосов
/ 19 июля 2010

Спасибо за комментарии, первоначальная версия этого ответа была неправильной.

Вот трюк с использованием вычисляемого столбца, который эффективно допускает обнуляемое уникальное ограничение в SQL Server:

create table NullAndUnique 
    (
    id int identity, 
    name varchar(50),
    uniqueName as case 
        when name is null then cast(id as varchar(51)) 
        else name + '_' end,
    unique(uniqueName)
    )

insert into NullAndUnique default values
insert into NullAndUnique default values -- Works
insert into NullAndUnique default values -- not accidentally :)
insert into NullAndUnique (name) values ('Joel')
insert into NullAndUnique (name) values ('Joel') -- Boom!

Обычно используется id, когда name равно нулю. + '_' позволяет избежать случаев, когда имя может быть числовым, например 1, которое может конфликтовать с id.

...