【问题标题】:SQL - Unique key across 2 columns of same table?SQL - 同一表的 2 列中的唯一键?
【发布时间】:2018-05-23 20:08:51
【问题描述】:

我使用 SQL Server 2016。我有一个名为“Member”的数据库表。

在该表中,我有这 3 列(出于我的问题的目的):

  • idMember [INT - 身份 - 主键]
  • memEmail
  • memEmailPartner

我想阻止一行使用表中已经存在的电子邮件。

两个电子邮件列都不是强制性的,因此它们可以留空 (NULL)。

如果我创建一个新成员:

如果不是空白,则为“memEmail”和“memEmailPartner”(独立)输入的值不应在 memEmail 或 memEmailPartner 列的任何其他行中找到。

因此,如果我想用电子邮件 (dominic@email.com) 创建一行,我一定不能在 memEmail 或 memEmailPartner 中找到该值的任何出现。

如果我更新现有会员:

我不能在 memEmail 或 memEmailPartner 中找到该值的任何匹配项,除非我正在更新已在 memEmail 或 memEmailPartner 中具有该值的行 (idMembre)。

--

根据我在 Google 上阅读的内容,应该可以使用基于函数的检查约束来做某事,但我无法做到。

有人能解决我的问题吗?

谢谢。

【问题讨论】:

  • ,而不是字段...
  • ,不是记录...
  • 我认为 OP 希望在 memEmailmemEmailPartner 两个列中都具有唯一性,即不是独立的。
  • 没错。肖恩提供的第一个解决方案对我不起作用。
  • 要使用索引强制执行此操作,您需要将结构更改为 idMember, email,emailType 然后它是一个简单的(过滤以忽略空值)唯一索引。

标签: sql sql-server unique-constraint unique-key check-constraint


【解决方案1】:

我可能完全误解了您的要求,但您似乎想要一个带有 IF EXISTS 条件的简单 upsert 查询。

DECLARE @emailAddress VARCHAR(255)= 'dominic@email.com', --dummy value
        @id INT= 2;                                      --dummy value

IF NOT EXISTS
(
   SELECT 1
   FROM @Member
   WHERE memEmail = @emailAddress
         OR memEmailPartner = @emailAddress
)
    BEGIN
        SELECT 'insert';
    END;
ELSE IF EXISTS 
(
  SELECT 1
  FROM @Member
  WHERE idMember = @id
)
    BEGIN
       SELECT 'update';
    END;

【讨论】:

    【解决方案2】:

    触发器是按照您的要求执行操作的传统方式。这是一个简单的演示;

    --if object_id('member') is not null drop table member
    go
    
    create table member (
        idMember INT Identity Primary Key,
        memEmail varchar(100),
        memEmailPartner varchar(100) 
    )
    go
    
    create trigger trg_member on member after insert, update as
    begin
        set nocount on
    
        if exists (select 1 from member m join inserted i on i.memEmail = m.memEmail and i.idMember <> m.idMember) or
           exists (select 1 from member m join inserted i on i.memEmail = m.memEmailPartner and i.idMember <> m.idMember) or
           exists (select 1 from member m join inserted i on i.memEmailPartner = m.memEmail and i.idMember <> m.idMember) or
           exists (select 1 from member m join inserted i on i.memEmailPartner = m.memEmailPartner and i.idMember <> m.idMember) 
        begin
            raiserror('Email addresses must be unique.', 16, 1)
            rollback
        end 
    end
    go
    
    insert member(memEmail, memEmailPartner) values('a@a.com', null), ('b@b.com', null), (null, 'c@c.com'), (null, 'd@d.com')
    go
    
    select * from member
    
    insert member(memEmail, memEmailPartner) values('a@a.com', null) -- should fail
    go
    insert member(memEmail, memEmailPartner) values(null, 'a@a.com') -- should fail
    go
    insert member(memEmail, memEmailPartner) values('c@c.com', null) -- should fail
    go
    insert member(memEmail, memEmailPartner) values(null, 'c@c.com') -- should fail
    go
    
    insert member(memEmail, memEmailPartner) values('e@e.com', null) -- should work
    go
    insert member(memEmail, memEmailPartner) values(null, 'f@f.com') -- should work
    go
    
    select * from member
    
    -- Make sure updates still work!
    update member set memEmail = memEmail, memEmailPartner = memEmailPartner
    

    我没有对此进行过广泛的测试,但如果你想尝试这种方法,它应该足以让你开始。

    StuartLC 注意到 UDF 检查约束在基于集合的更新和/或各种其他条件下失败的可能性,触发器没有这个问题。

    Stuart 还建议重新考虑这是否应该是真正的数据库约束或通过其他地方的业务逻辑进行管理。我倾向于同意 - 我的直觉是,迟早你会遇到需要重复使用电子邮件地址的情况,或者以其他方式不是严格唯一的。

    【讨论】:

      【解决方案3】:

      TL;DR

      需要重新考虑在数据库中应用这种业务规则逻辑的智慧 - 此检查可能更适合您的应用程序,或者充当插入门卫而不是直接将新行插入到的存储过程桌子。

      忽略警告

      也就是说,我确实相信您想要的东西在约束 UDF 中是可能的,尽管可能会带来严重的性能后果*1,并且可能容易出现race conditions in set based updates

      这是一个用户定义的函数,它在两列中应用独特的电子邮件逻辑。请注意,在检查约束时,该行是IN the table already,因此新行本身需要从重复检查中排除。

      我的代码也依赖于 ANSI NULL 行为,即谓词 NULL = NULLX IN (NULL) 都返回 NULL,因此被排除在失败检查之外(为了满足您的要求,即 NULLS 不会使规则失败)。

      我们还需要检查插入的两个新列是否非空,但重复。

      所以这是一个 UDF 进行检查:

      CREATE FUNCTION dbo.CheckUniqueEmails(@id int, @memEmail varchar(50), 
                                            @memEmailPartner varchar(50))
      RETURNS bit
      AS 
      BEGIN
         DECLARE @retval bit;
         IF @memEmail = @memEmailPartner
           OR EXISTS (SELECT 1 FROM MyTable WHERE memEmail IS NOT NULL 
                      AND memEmail IN(@memEmail, @memEmailPartner) AND idMember <> @id)
           OR EXISTS (SELECT 1 FROM MyTable WHERE memEmailPartner IS NOT NULL 
                      AND memEmailPartner IN(@memEmail, @memEmailPartner) AND idMember <> @id)
           SET @retval = 0
         ELSE 
           SET @retval = 1;
        RETURN @retval;
      END;
      GO
      

      然后在 CHECK 约束中强制执行:

      ALTER TABLE MyTable ADD CHECK (dbo.CheckUniqueEmails(
                                             idMember, memEmail, memEmailPartner) = 1);
      

      我已经发了SQLFiddle up here

      取消注释“失败”的测试用例以确保上述检查约束有效。

      我没有通过更新对此进行测试,根据 Martin 对链接的建议,这可能会在插入多行时中断。

      *1 - 我们需要两个电子邮件地址列的索引。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-29
        • 1970-01-01
        相关资源
        最近更新 更多