我有这样一种情况,需要对一组列强制执行唯一约束,但仅针对某一列的一个值。
例如,我有一个表Table(ID, Name, RecordStatus)。
RecordStatus 只能是 1 或 2 (表示活动或删除),当 RecordStatus = 1 时,我想在 (ID, RecordStatus) 上创建一个唯一约束,因为如果有多个已删除的记录具有相同的 ID ,我并不在意。
除了编写触发器,我还能做到吗?
我正在使用 SQL Server 2005。
我有这样一种情况,需要对一组列强制执行唯一约束,但仅针对某一列的一个值。
例如,我有一个表Table(ID, Name, RecordStatus)。
RecordStatus 只能是 1 或 2 (表示活动或删除),当 RecordStatus = 1 时,我想在 (ID, RecordStatus) 上创建一个唯一约束,因为如果有多个已删除的记录具有相同的 ID ,我并不在意。
除了编写触发器,我还能做到吗?
我正在使用 SQL Server 2005。
看,过滤索引来了。根据文档(重点是我的):
过滤索引是一种优化的非聚集索引,特别适合于选择来自定义数据子集的查询。 它使用筛选谓词对表中的一部分行进行索引。 设计良好的过滤索引可以提高查询性能,同时与全表索引相比减少索引维护和存储成本。
这里还有一个将唯一索引与筛选谓词相结合的示例:
create <b>unique</b> index MyIndex
on MyTable(ID)
<b>where RecordStatus = 1;</b>
当 RecordStatus
为 1
时,这实际上强制要求 ID
的唯一性。
创建该索引后,违反唯一性将引发错误:
Msg 2601, Level 14, State 1, Line 13
Cannot insert duplicate key row in object 'dbo.MyTable' with unique index 'MyIndex'. The duplicate key value is (9999).
注意:过滤索引是在SQL Server 2008中引入的。对于早期版本的SQL Server,请参见此答案。
ansi_padding
,因此在创建过滤索引之前,请确保执行 SET ANSI_PADDING ON
打开此选项。 - naXa stands with Ukraine添加一个类似这样的检查约束。不同之处在于,如果状态为1且计数大于0,则返回false。
http://msdn.microsoft.com/zh-cn/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;
ALTER TABLE
中的CheckActiveCountConstraint
? - masTOR RecordStatus = 2
以使约束也允许插入状态为2的记录,因此它必须像这样:ALTER TABLE CheckConstraint ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1) OR RecordStatus = 2);
...或许是因为原始示例并没有考虑添加状态为2的记录,而是在某些时候仅从1更新到2。 - gbdavid您可以用一种非常巧妙的方法来做到这一点...
在您的表上创建一个模式绑定视图。
CREATE VIEW Whatever SELECT * FROM Table WHERE RecordStatus = 1
现在,在您想要的字段上为该视图创建唯一约束。
关于模式绑定视图的一点说明,如果您更改底层表,您将不得不重新创建视图。因此有很多注意事项。
如果您不能像Bill建议的那样使用NULL作为RecordStatus,您可以将他的想法与基于函数的索引相结合。创建一个函数,如果RecordStatus不是您要考虑的值之一,则返回NULL(否则返回RecordStatus),并在其上创建索引。
这将具有优势,即您不必明确地检查表中的其他行以满足约束条件,这可能会导致性能问题。
我应该说我完全不了解SQL Server,但我已经成功地在Oracle中使用了这种方法。
因为你要允许重复,所以唯一约束将不起作用。你可以为RecordStatus列创建一个检查约束,并为INSERT创建一个存储过程,在插入重复的ID之前检查现有的活动记录。