我如何有一个引用另一个表的检查约束?

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 中使用外键约束将一个表的结构复制到另一个表?

外键约束

如何在不将实例模型添加到数据库的情况下向实体模型添加FK约束?

为啥在MySQL数据库中建立检查约束不成功呢,语句是这样的

检查约束以确保日期不在未来?

在多列上检查约束