一组记录中的唯一约束,其中某些值相同

时间:2010-07-30 21:57:26

标签: sql sql-server database sql-server-2005 constraints

DBMS: 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

但这应该(超过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

有没有办法用表约束来做到这一点?

更新 我现在已经接受了Martin Smith的回答,尽管我将在即将发布的版本中推动JohnFx的重构,因为它是最好的长期解决方案。但是我想根据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. 使用@tempTable而不是 根据udf的要求

    tempTable(在内存中写入磁盘)

  2. 将@ fk1作为参数传递,因此我可以在一个中选择唯一性 一组fk1值。
  3. 棘手的也必须传递isPrimary,即使它不是 必要的逻辑 功能,否则SQL2005 优化器不会运行检查 isPrimary时的约束 更新。

4 个答案:

答案 0 :(得分:9)

因为我严重损坏了第一个答案而开始了一个新的答案。

听起来你可以通过重新思考一下你的表设计来解决这个问题,以避免让你暴力成为实施业务规则的约束。

如何从MyTable中删除IsPrimary列并将PrimaryPersonID列添加到引用主要人员的另一个表中?

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

答案 1 :(得分:6)

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

  1. 创建一个过滤IsPrimary = 1的索引视图,并为该视图添加唯一索引。
  2. 创建一个触发器,确保只有一个可以是主要的。
  3. 将您的逻辑封装到存储过程中,并强制用户通过存储过程从您的表中插入或更新。

答案 2 :(得分:4)

在检查约束中使用UDF可能会在snapshot isolationmultirow updates下失败。

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

CASE WHEN isPrimary = 1 THEN fk1 ELSE -pk1 END

然后为其添加一个唯一约束。或者如果不能做出那个假设那么可能

CASE WHEN isPrimary = 0 THEN 1.0/pk1 ELSE fk1 END

答案 3 :(得分:3)

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

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

尝试一下,让我知道它是否有效。虽然不是很优雅..