跨两列的唯一约束

Tug*_*ain 4 sql-server constraint

我需要添加一个包含两列的约束,说明其中一列中是否存在任何给定的值,然后:
1)它不能在同一列中重复。
2) 它也不能在其他列中重复。

我们正在寻找的约束是PrimaryEmailSecondaryEmail

这将是无效的:

UserId    PrimaryEmail       SecondaryEmail
231       joe@yahoo.com      Null
424       smo@gmail.com      joey@yahoo.com
Run Code Online (Sandbox Code Playgroud)

因为“joe@yahoo.com”出现在第一列中,因此无论它在哪一行,它都不能出现在第二列中。

是否可以在 SQL Server 2008 中定义这种类型的约束?


我们首先定义了一个只用于电子邮件的表,但我们已经从该模型恢复到支持两个硬列,原因有很多,包括:查询速度、查询复杂性以及用户使用多个电子邮件帐户的概率按顺序主动降低一个后的数量级。


这定义了一个传统的两列约束,但它在两列之间的每行基础上,并没有给我们我们所追求的:

CREATE UNIQUE NONCLUSTERED INDEX idx_UniqueEmail_notnull
ON UserProfile (PrimaryEmail, SecondaryEmail)
WHERE PrimaryEmail IS NOT NULL and SecondaryEmail IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 6

以声明方式建议的表结构不可能做到这一点。您需要触发器来强制执行此操作。

然而,两列上的唯一索引以及一对带有标量 UDF 的检查约束非常接近。

CREATE TABLE UserProfile
  (
     Id             INT PRIMARY KEY,
     PrimaryEmail   VARCHAR(100),
     SecondaryEmail VARCHAR(100)
  )

CREATE UNIQUE INDEX IX1
  ON UserProfile(PrimaryEmail)

CREATE UNIQUE INDEX IX2
  ON UserProfile(SecondaryEmail)

go

CREATE FUNCTION dbo.EmailInUseAsPrimary (@Email VARCHAR(100))
RETURNS BIT
AS
  BEGIN
      RETURN
        (SELECT COUNT(*)
         FROM   UserProfile WITH (READCOMMITTEDLOCK)
         WHERE  PrimaryEmail = @Email)
  END;

go

CREATE FUNCTION dbo.EmailInUseAsSecondary (@Email VARCHAR(100))
RETURNS BIT
AS
  BEGIN
      RETURN
        (SELECT COUNT(*)
         FROM   UserProfile WITH (READCOMMITTEDLOCK)
         WHERE  SecondaryEmail = @Email)
  END;

GO

ALTER TABLE UserProfile
  ADD CHECK ( dbo.EmailInUseAsPrimary(SecondaryEmail) = 0), 
      CHECK ( dbo.EmailInUseAsSecondary(PrimaryEmail) = 0)
Run Code Online (Sandbox Code Playgroud)

原因READCOMMITTEDLOCK是为了避免快照隔离问题

上述方法的一个问题是,由于约束是通过 RBAR 评估的,它可能会使一些应该成功的事务失败。

对于示例数据

INSERT INTO UserProfile
VALUES (1, 'abc@abc.com', 'def@def.com'),
       (2, 'ghi@ghi.com', 'jkl@jkl.com')
Run Code Online (Sandbox Code Playgroud)

这个说法不成立

UPDATE UserProfile
SET PrimaryEmail = CASE Id WHEN 1 THEN 'jkl@jkl.com' WHEN 2 THEN 'def@def.com' END,
    SecondaryEmail = CASE Id WHEN 1 THEN 'ghi@ghi.com' WHEN 2 THEN 'abc@abc.com' END  
Run Code Online (Sandbox Code Playgroud)

即使在交易结束时,限制条件会得到满足。但也许您执行这种更新(在类型和人员之间交换电子邮件地址)的可能性很小,因此可以忽略。


Dav*_*ett 6

根据您正在建模的内容,无论如何我可能会看到此限制的问题:对于我们的某些客户,某些人的辅助联系地址是整个团队的共享收件箱 - 因此,如果没有响应(或外出回复)发送给个人的消息。

在不使用诸如 Martin 描述的触发器方法之类的编程逻辑的情况下,以声明方式强制执行所需限制的唯一方法是将电子邮件地址视为单独的实体,而不仅仅是您所描述的您已经考虑过的人的属性:

Person         EmailAddress    
--------       ----------------
pID (PK)  <--  pID (FK) (PK)   
Name           Type     (PK)   
Blah           Address (unique)
Run Code Online (Sandbox Code Playgroud)

个人 ID 和地址类型上的主键强制(因为主键意味着唯一索引)每个人最多拥有每种类型(主要、次要)一个地址,并且 EmailAddress.Address 上的唯一索引强制没有两个人拥有相同的地址address 不管是什么地址类型。

查询复杂度

这可以使用视图来解决。您是对的,这可能会引入“查询速度”差异,但我很确定它们不会显着。

查询速度

如果这是一个重大问题,那么您仍然可以在 Person 表中拥有主要和次要地址列,并在使用 EmailAddress 表上的触发器时维护它们。

然后,您需要选择如果开发人员/用户尝试直接更新 Person 表中的地址会发生什么:您可以使用触发器相应地更新 EmailAddress,或者如果用户尝试为这些地址设置值,则使用触发器引发错误直接列 - 尽管在这两种情况下,您都在权衡插入和更新性能以换取选择速度/便利性。当然,您现在正在使用编程逻辑来控制事物,因此复杂性类似于 Martin 的建议。

另一种选择是索引视图,但我认为它们仅在企业版上可用,这可能会根据您的项目大小排除它们的使用。

这可能都没有实际意义,因为正如我上面所说:我怀疑这种结构会产生显着的性能差异。我希望它需要大量数据集才能可靠地测量差异,更不用说最终用户注意到了。

并且用户使用多个邮箱的概率在一个之后按数量级递减

只要你不关心那个有很多的用户,如果他不能全部记录下来,他会玩得很开心!尽管如此,大多数系统无论如何都只接受一个相关地址的可能性。