我如何有一个引用另一个表的检查约束?
Posted
技术标签:
【中文标题】我如何有一个引用另一个表的检查约束?【英文标题】:How do I have a check constraint that refers to another table? 【发布时间】:2010-09-16 15:43:01 【问题描述】:我在 SQL Server 2008 数据库中有以下表:
tblItem,有一个ItemID字段;
tblGoodItem,也有ItemID字段,外键指向tblItem;
tblBadItem,也有一个ItemID字段,还有一个外键指向tblItem。
一件物品不能既是好物又是坏物;它必须是一个或另一个。但是,无论物品的好坏,它都必须是一个物品。
我的问题是:如何向 tblGoodItem 和 tblBadItem 中的 ItemID 字段添加约束,以使两个表中都不能存在 ItemID 值?
我在 Stack Overflow 上阅读了一些关于类似问题的回复,我正在考虑这个解决方案:
创建一个视图 vwItem,它将 tblGoodItem on tblBadItem on ItemID 连接起来。
编写一个 UDF fnItem,它对 vwItem 进行查询以查看视图中存在多少条记录。
有一个调用 fnItem 并验证返回值是否为 0 的约束。
这是最好的主意吗?有人有更好的主意吗?
【问题讨论】:
【参考方案1】:添加一列 tblItem.ItemType 列。此列在任何给定行上只能有一个值(显然)。在 ItemID,ItemType 上添加唯一约束。
现在的诀窍:很少有人记得这一点,但外键可以引用唯一约束的列。
CREATE TABLE tblItem (
ItemID INT PRIMARY KEY,
ItemType CHAR(1),
UNIQUE KEY (ItemID, ItemType)
);
CREATE TABLE tblGoodItem (
ItemID INT PRIMARY KEY,
ItemType CHAR(1),
CHECK (ItemType='G')
FOREIGN KEY (ItemID, ItemType) REFERENCES tblItem(ItemID, ItemType)
);
CREATE TABLE tblBadItem (
ItemID INT PRIMARY KEY
ItemType CHAR(1),
CHECK (ItemType='B')
FOREIGN KEY (ItemID, ItemType) REFERENCES tblItem(ItemID, ItemType)
);
如果将每个子表中的 ItemType 限制为固定值,则 tblItem 中的给定行只能由一个子表引用。
不过,将物品从好转坏需要三个步骤:
-
从 tblGoodItem 中删除行
在 tblItem 中更新行的 ItemType
在 tblBadItem 中插入行
【讨论】:
你领先我几秒。 我非常喜欢这个想法。无需担心从坏到好的变化——事实上,我特别不希望发生这样的变化。【参考方案2】:去掉 tblGoodItem 和 tblBadItem 并创建一个 ItemType="G" 或 "B" 的新表,并在 ItemID 上放置一个唯一索引或键,然后对 tblItem 没有约束。
【讨论】:
恐怕不行。好物品有一些坏物品没有的属性,反之亦然。 如果只有几个,在 10 个左右的差异下,那么我仍然将它们组合起来,让它们允许 NULL 并添加一个检查约束以根据 Type 列要求它们【参考方案3】:我可能在这里不了解您的业务需求,但您为什么希望有一个单独的表来区分好商品和坏商品?这些不是对同一事物的抽象吗?
为什么不使用 isBadItem 标志或更具体地说是 itemConditionStatus 列。
【讨论】:
【参考方案4】:在 tblItem 中,添加 itemType 列。有一个检查约束来确保 itemType 是好是坏。在 (ItemID, itemType ) 上创建唯一约束
将 itemType 列添加到 bad 和 good items 表中。有一个检查约束来确保 itemType 在好表中是好的,在坏表中是坏的。
【讨论】:
【参考方案5】:您不能在 CHECK
约束中使用 SELECT
语句 - 这并不是它们的真正目的。
我认为您最好的选择是在 ItemId 中编写一个 UDF 传递并检查它是否存在。对于这种情况,它确实是最简单的选择。
我添加了一些测试数据和一个示例函数。
CREATE FUNCTION dbo.fn_CheckItems(@itemId INT) RETURNS BIT
AS BEGIN
DECLARE @i INT,
@rv BIT
SET @i = 0
IF (SELECT COUNT(*) FROM tblBadItem WHERE ItemId = @ItemId) > 0
BEGIN
SET @i = 1
END
IF (SELECT COUNT(*) FROM tblGoodItem WHERE ItemId = @ItemId) > 0
BEGIN
SET @i = @i + 1
END
IF (@i > 1)
BEGIN
SET @rv = 1
END
ELSE
BEGIN
SET @rv =0
END
RETURN @rv
END
GO
CREATE TABLE tblItem (
ItemID INT IDENTITY(1,1) PRIMARY KEY,
DateAdded DATETIME
)
GO
CREATE TABLE tblGoodItem (
ItemID INT PRIMARY KEY,
CHECK (dbo.fn_CheckItems(ItemId) = 0)
)
GO
CREATE TABLE tblBadItem (
ItemID INT PRIMARY KEY,
CHECK (dbo.fn_CheckItems(ItemId) = 0)
)
GO
INSERT INTO tblItem (DateAdded)
VALUES (GETDATE())
INSERT INTO tblGoodItem(ItemID)
SELECT ItemId FROM tblItem
--This statement will fail as the ItemId is already in GoodItems
INSERT INTO tblBadItem(ItemID)
SELECT ItemId FROM tblItem
DROP TABLE tblItem
DROP TABLE tblGoodItem
DROP TABLE tblBadItem
DROP FUNCTION dbo.fn_CheckItems
【讨论】:
@AlexKuznetsov - 你能否详细说明为什么这不起作用。我创建了一个测试,它工作正常? 抱歉,我错了。但是,这样的 UDF 非常慢,如果您使用快照隔离,您可能会在并发下偶尔得到错误的结果。 @AlexKuznetsov - 没问题。我必须承认我在使用快照复制时没有使用过这个,所以我不能不同意或同意。以上是关于我如何有一个引用另一个表的检查约束?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 psql 中使用外键约束将一个表的结构复制到另一个表?