代码之家  ›  专栏  ›  技术社区  ›  Adam J.R. Erickson

一组记录中某个值相同的唯一约束

  •  8
  • Adam J.R. Erickson  · 技术社区  · 14 年前

    数据库管理系统: MS SQL Server 2005,标准

    我想做一个表约束,使只有一条记录在表的一个子集中有一个特定的值(在该子集中,行共享一个特定列中的值)。这有可能吗?

    例子: 我在myTable中有一些记录,它们有一个非唯一的外键(fk1),还有一个名为isprimary的位列,它指出这个特殊的外键应该被我们的应用程序用于特殊的逻辑。

    在摘要中,它看起来是这样的:

    myTable
    -------------
    pk1       (int, not null)
    name      (varchar(50), null)
    fk1       (int, not null)
    isPrimary (bit, not null)
    

    我想确保 一个而且只有一个 对于fk1的每个唯一值,将isprimary标志设置为1进行记录。

    数据示例: 这应该是合法的:

    pk1     name     fk1    isPrimary
    ----    -----    -----  ----------
    1       Bill     111    1
    2       Tom      111    0
    3       Dick     222    1
    4       Harry    222    0
    

    但这应该 be(超过一个,其中fk=111):

    pk1     name     fk1    isPrimary
    ----    -----    -----  ----------
    1       Bill     111    1
    2       Tom      111    1
    3       Dick     222    1
    4       Harry    222    0
    

    这也不应该(当fk=222时没有):

    pk1     name     fk1    isPrimary
    ----    -----    -----  ----------
    1       Bill     111    1
    2       Tom      111    0
    3       Dick     222    0
    4       Harry    222    0
    

    有没有一种方法可以通过表约束来实现这一点?

    更新 我现在已经给出了马丁·史密斯的答案,不过我会在即将发布的版本中推动约翰克斯的重构,因为这是最好的长期解决方案。不过,我想根据Raze2Dust的回答发布我更新的UDF,以防未来的读者认为这更适合他们的需求。

    CREATE FUNCTION [dbo].[OneIsPrimaryPerFK1](@fk1 INT, @dummyIsPrimary BIT)
    RETURNS INT
    AS 
    BEGIN
        DECLARE @retval INT;
        DECLARE @primarySum INT;
        SET @retval = 0;
        DECLARE @TempTable TABLE (
        fk1 INT,
        PrimarySum INT)
    
    INSERT INTO @TempTable
        SELECT fk1, SUM(CAST(isPrimary AS INT)) AS PrimarySum
        FROM FacAdmin
        WHERE fk1 = @fk1
        GROUP BY fk1;
    
        SELECT @primarySum = PrimarySum FROM @TempTable;
        IF(@primarySum=1)
            BEGIN
                SET @retval = 1
            END
        RETURN @retval
    END;
    

    变化:

    1. 使用@诱人而不是

      根据UDF的要求,具有诱惑力(内存V.写入磁盘)

    2. 将@fk1作为参数传递,以便在一个参数内选择唯一性 FK1值组。
    3. 狡猾的 即使不是,也必须通过初审 逻辑上的 函数,否则为sql2005 优化器将不运行检查 当IsPrimary为 更新。
    4 回复  |  直到 14 年前
        1
  •  4
  •   Martin Smith    14 年前

    在检查约束中使用UDF可能会失败 snapshot isolation multirow updates .

    假设您的所有fk1和pk1值当前(并且将始终为)为正数,您可以使用以下定义创建计算列

    CASE WHEN isPrimary = 1 THEN fk1 ELSE -pk1 END
    

    然后添加一个唯一的约束。或者如果不能做出这样的假设,那么也许

    CASE WHEN isPrimary = 0 THEN 1.0/pk1 ELSE fk1 END
    
        2
  •  9
  •   JohnFx    14 年前

    自从我把第一个答案弄错以后,我开始了一个新的答案。

    听起来您可以通过重新考虑一下表设计来解决这个问题,以避免让您强行执行业务规则的约束。

    从mytable中删除isprimary列并将primaryPersonid列添加到引用primary person的另一个表中如何?

    这样,结构本身就会强制FK表中只有1个条目是每个人的主要条目。

        3
  •  6
  •   Thomas    14 年前

    SQL 2005不提供将WHERE子句应用于唯一索引(如SQL 2008)的功能。但是,有几种方法可以解决SQL 2005中的问题:

    1. 创建筛选isprimary=1的索引视图,并向该视图添加唯一索引。
    2. 创建一个触发器,确保只有一个触发器是主触发器。
    3. 将逻辑封装到存储过程中,并强制用户通过存储过程从表中插入或更新。
        4
  •  3
  •   Hari Menon    14 年前

    您可以尝试创建一个函数,然后使用检查约束:

    CREATE FUNCTION ChkFn()
    RETURNS INT
    AS 
    BEGIN
       DECLARE @retval INT
       DECLARE @distinct INT
       DECLARE @top INT
       SET @retval = 0
       SELECT fk1 AS ForeignKey, SUM(isPrimary) AS PrimarySum
        INTO #TempTable 
        FROM myTable
        GROUP BY fk1
       SELECT @distinct = COUNT(DISTINCT(PrimarySum)) FROM #TempTable
       SELECT @top = top PrimarySum FROM #TempTable
       IF(@distinct=1 AND @top=1)
        BEGIN
        @retval = 1
        END
       RETURN @retval
    END;
    GO
    
    ALTER TABLE myTable
    ADD CONSTRAINT chkFkPk CHECK (dbo.ChekFn() = 1 );
    GO
    

    试试看,让我知道它是否有效。不过不是很优雅……