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;
的变化:
答案 0 :(得分:9)
因为我严重损坏了第一个答案而开始了一个新的答案。
听起来你可以通过重新思考一下你的表设计来解决这个问题,以避免让你暴力成为实施业务规则的约束。
如何从MyTable中删除IsPrimary列并将PrimaryPersonID列添加到引用主要人员的另一个表中?
这样,结构本身就会强制执行1,并且FK表中只有1个条目是每个人的主要条目。
答案 1 :(得分:6)
SQL 2005不提供将Where子句应用于SQL 2008等唯一索引的功能。但是,有几种方法可以解决SQL 2005中的问题:
答案 2 :(得分:4)
在检查约束中使用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
答案 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
尝试一下,让我知道它是否有效。虽然不是很优雅..