条件唯一约束


92

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

因此,例如,我有一个像Table(ID,Name,RecordStatus)的表。

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

除了编写触发器,我还能这样做吗?

我正在使用SQL Server 2005。


1
这种设计是一个普遍的痛苦。您是否考虑过更改设计,以便从表中物理删除概念上“已删除”的记录,甚至可能将其移至“归档”表?
2009年

1
...由于无法编写唯一的约束来强制执行简单的键,因此应将其视为“代码异味”,IMO。如果您无法更改设计(SQL DDL),因为许多其他表都引用了该表,那么我想您的SQL DML也会因此遭受损失,即您必须记住添加... AND Table.RecordStatus = 1'涉及此表的大多数搜索条件和联接条件,并且有时不可避免地会遇到细微的错误。
2009年

Answers:


36

添加这样的检查约束。区别在于,如果Status = 1且Count> 0,则将返回false。

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;

我查看了表级别的检查约束,但没有找到任何将要插入或更新的值传递给函数的方法,您知道如何吗?
np-hard

好的,我发布了一个示例脚本,可以帮助您证明我在说什么。我测试了它,而且效果很好。如果您查看两条注释行,则会看到我收到的消息。注意,在我的实现中,我仅确保您不能添加具有相同ID的第二项,如果已经有一个处于活动状态的Id,则不能添加。您可以修改逻辑,以便如果有活动的逻辑,则不能添加具有相同ID的任何项目。使用这种模式,可能性几乎是无限的。
D. Patrick

我希望触发器中使用相同的逻辑。“标量函数中的查询...如果您的CHECK约束依赖于一个查询并且任何更新都影响了多行,则可能会产生大问题。发生的情况是,在语句完成之前,每行都要检查一次约束这意味着语句的原子性被破坏,并且该函数将以不一致的状态公开给数据库。结果是不可预测的且不准确的。” 请参阅:blogs.conchango.com/davidportas/archive/2007/02/19/…– 2009
某一天

有一天,这只是部分正确。数据库的行为始终如一且可预测。在将行添加到表之后以及在dbms提交事务之前,将执行检查约束,您可以依靠它。该博客谈论的是一个非常独特的问题,您需要对一组插入而不是一次仅执行一个插入执行约束。ashish一次要求对一个插入进行限制,并且该限制将准确,可预测且一致地起作用。很抱歉,听起来很简洁;我的字符用完了。
D. Patrick

3
这对插入非常有用,但似乎对更新不起作用。EG在我没想到的情况下,在其他插入内容之后添加此内容即可。插入CheckConstraint值(1,'No ProblemsA',2); 更新CheckConstraint设置Recordstatus = 1,其中name ='No ProblemsA'–
dwidel

148

看,过滤后的索引。从文档(重点是我的):

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

这是结合唯一索引和过滤谓词的示例:

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

这从本质上强制了IDwhen RecordStatusis的唯一性1

创建该索引后,违反唯一性将引发错误:

消息2601,级别14,状态1,第13
行无法在具有唯一索引“ MyIndex”的对象“ dbo.MyTable”中插入重复的键行。重复的密钥值为(9999)。

注意:筛选后的索引是SQL Server 2008中引入的。有关SQL Server的早期版本,请参见此答案


请注意,SQL Server需要ansi_padding过滤索引,因此请确保SET ANSI_PADDING ON在创建过滤索引之前执行该选项以将其打开。
naXa

10

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


2
这实际上是非常聪明的卡尔。这本身并不是对这个问题的答案,但这是一个很好的解决方案。如果表中有很多行,那也可以加快查找活动记录的速度,因为您可以查看活动记录表。因为唯一约束使用索引而不是我在下面编写的必须执行计数的检查约束,所以它也会加快约束的速度。我喜欢。
D. Patrick

3

您可以以一种非常怪异的方式来做到这一点...

在表上创建一个架构绑定的视图。

从SELECT WHERE RecordStatus = 1的表创建任何内容

现在,在具有所需字段的视图上创建唯一约束。

但是,有一个关于架构绑定视图的说明,如果您更改基础表,则必须重新创建视图。因此有很多陷阱。


这是一个很好的建议,而不是“ hacky”。这是有关此筛选索引替代项的更多信息。
斯科特·惠特洛克

这是一个坏主意。问题是不是。
FabianoLothor

我曾经使用过架构绑定视图,并且从未重复过该错误。与他们一起工作可能会很痛苦。并不是如果您更改基础表就必须重新创建视图-您可能必须对所有视图都执行此操作,至少在SQL Server中是如此。这是因为您必须先删除视图才能更改表,而如果不先删除对它的引用,则可能无法执行该操作。哦,加上存储空间可能会出现问题-要么是因为空间原因,要么是由于插入和更新的成本增加。
MattW

1

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


1

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

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

我应该说我一点都不了解SQL Server,但是我已经在Oracle中成功使用了这种方法。


好主意,但是在sql server中没有基于索引的函数,尽管感谢您的回答
np-hard
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.