我遇到一种情况,我需要对一组列强制执行唯一约束,但只对列的一个值强制执行。
所以例如我有一个像Table(ID,Name,RecordStatus)这样的表。
RecordStatus只能有一个值1或2(活动或删除),我想只在RecordStatus = 1时才在(ID,RecordStatus)上创建一个唯一约束,因为我不在乎是否有多个已删除的记录具有相同的ID。
除了编写触发器,我可以这样做吗?
我正在使用SQL Server 2005。
答案 0 :(得分:125)
看哪,the filtered index。从文档(强调我的):
过滤索引是一种优化的非聚簇索引,特别适用于覆盖从明确定义的数据子集中进行选择的查询。 它使用过滤谓词来索引表中行的一部分。精心设计的过滤索引可以提高查询性能,并与全表索引相比降低索引维护和存储成本。
这是一个将唯一索引与过滤谓词组合在一起的示例:
create unique index [MyIndex]
on [MyTable]([ID])
where [RecordStatus] = 1
当ID
为RecordStatus
时,这基本上强制了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,一个类似的问题,我认为这对许多人来说仍然有用。虽然将已删除的记录移动到另一个表可能是更好的解决方案,但对于那些不想移动记录的人可以使用链接答案中的想法,如下所示。