условное уникальное ограничение - PullRequest
82 голосов
/ 15 мая 2009

У меня есть ситуация, когда мне нужно применить уникальное ограничение для набора столбцов, но только для одного значения столбца.

Так, например, у меня есть таблица типа Table (ID, Name, RecordStatus).

RecordStatus может иметь значение только 1 или 2 (активное или удаленное), и я хочу создать уникальное ограничение для (ID, RecordStatus) только тогда, когда RecordStatus = 1, так как мне все равно, если есть несколько удаленных записей с тем же идентификатором.

Помимо написания триггеров, могу ли я это сделать?

Я использую SQL Server 2005.

Ответы [ 6 ]

125 голосов
/ 01 марта 2011

Вот, отфильтрованный индекс . Из документации (выделено мое):

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

И вот пример, объединяющий уникальный индекс с предикатом фильтра:

create <b>unique</b> index MyIndex
on MyTable(ID)
<b>where RecordStatus = 1;</b>

По существу, это обеспечивает уникальность ID, когда RecordStatus равно 1.

Примечание: отфильтрованный индекс был введен в SQL Server 2008. Для более ранних версий SQL Server см. этот ответ .

33 голосов
/ 15 мая 2009

Добавьте ограничение проверки следующим образом. Разница в том, что вы вернете false, если Status = 1 и Count> 0.

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

CREATE TABLE CheckConstraint
(
  Id TINYINT,
  Name VARCHAR(50),
  RecordStatus TINYINT
)
GO

CREATE FUNCTION CheckActiveCount(
  @Id INT
) RETURNS INT AS BEGIN

  DECLARE @ret INT;
  SELECT @ret = COUNT(*) FROM CheckConstraint WHERE Id = @Id AND RecordStatus = 1;
  RETURN @ret;

END;
GO

ALTER TABLE CheckConstraint
  ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));

INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);

INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);

SELECT * FROM CheckConstraint;
-- Id   Name         RecordStatus
-- ---- ------------ ------------
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  1
-- 2    Oh no!       1
-- 2    Oh no!       2

ALTER TABLE CheckConstraint
  DROP CONSTRAINT CheckActiveCountConstraint;

DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;
10 голосов
/ 15 мая 2009

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

3 голосов
/ 15 мая 2009

Вы можете сделать это по-настоящему хакерским способом ...

Создайте привязку схемы к вашей таблице.

СОЗДАТЬ ВИД SELECT * FROM Table ГДЕ RecordStatus = 1

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

Одно замечание о представлениях со схемой, однако, если вы измените базовые таблицы, вам придется воссоздать представление. Из-за этого много ошибок.

1 голос
/ 15 мая 2009

Если вы не можете использовать NULL в качестве RecordStatus, как предложил Билл, вы можете объединить его идею с индексом на основе функций. Создайте функцию, которая возвращает NULL, если RecordStatus не является одним из значений, которые вы хотите учитывать в своем ограничении (и RecordStatus в противном случае), и создайте индекс для этого.

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

Я должен сказать, что вообще не знаю SQL-сервер, но я успешно использовал этот подход в Oracle.

1 голос
/ 15 мая 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...