条件唯一约束

时间:2009-05-14 21:57:07

标签: sql sql-server sql-server-2005

我遇到一种情况,我需要对一组列强制执行唯一约束,但只对列的一个值强制执行。

所以例如我有一个像Table(ID,Name,RecordStatus)这样的表。

RecordStatus只能有一个值1或2(活动或删除),我想只在RecordStatus = 1时才在(ID,RecordStatus)上创建一个唯一约束,因为我不在乎是否有多个已删除的记录具有相同的ID。

除了编写触发器,我可以这样做吗?

我正在使用SQL Server 2005。

7 个答案:

答案 0 :(得分:125)

看哪,the filtered index。从文档(强调我的):

  

过滤索引是一种优化的非聚簇索引,特别适用于覆盖从明确定义的数据子集中进行选择的查询。 它使用过滤谓词来索引表中行的一部分。精心设计的过滤索引可以提高查询性能,并与全表索引相比降低索引维护和存储成本。

这是一个将唯一索引与过滤谓词组合在一起的示例:

create unique index [MyIndex]
on [MyTable]([ID])
where [RecordStatus] = 1

IDRecordStatus时,这基本上强制了1的唯一性。

注意:过滤的索引是在SQL Server 2008中引入的。对于早期版本的SQL Server,请参阅this answer

答案 1 :(得分:33)

添加这样的检查约束。区别在于,如果Status = 1且Count>,您将返回false。 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;

答案 2 :(得分:10)

您可以将已删除的记录移动到缺少约束的表中,并且可能使用具有两个表的UNION的视图来保留单个表的外观。

答案 3 :(得分:3)

你可以用一种非常黑客的方式做到这一点......

在桌面上创建架构式视图。

创建视图无论如何 SELECT * FROM Table WHERE RecordStatus = 1

现在使用您想要的字段在视图上创建一个唯一约束。

关于模式绑定视图的一个注意事项,如果更改基础表,则必须重新创建视图。由于这个原因,有很多陷阱。

答案 4 :(得分:1)

因为,您将允许重复,因此唯一约束将不起作用。您可以为RecordStatus列创建检查约束,并为插入重复ID之前检查现有活动记录的INSERT创建存储过程。

答案 5 :(得分:1)

如果你不能像Bill建议的那样使用NULL作为RecordStatus,你可以将他的想法与基于函数的索引结合起来。如果RecordStatus不是您要在约束中考虑的值之一(否则为RecordStatus),并创建一个索引,则创建一个返回NULL的函数。

这样做的好处是您不必在约束中明确检查表中的其他行,这可能会导致性能问题。

我应该说我根本不懂SQL服务器,但我已经在Oracle中成功使用了这种方法。

答案 6 :(得分:0)

对于那些仍在寻找解决方案的人,我遇到了一个很好的 answer,一个类似的问题,我认为这对许多人来说仍然有用。虽然将已删除的记录移动到另一个表可能是更好的解决方案,但对于那些不想移动记录的人可以使用链接答案中的想法,如下所示。

  • 当记录可用/活动时设置已删除=0。
  • 在标记行时设置已删除= 已删除。