Sqlite 确保产品和标签属于同一帐户的数据库级约束?

Sqlite 确保产品和标签属于同一帐户的数据库级约束?,sqlite,foreign-keys,constraints,Sqlite,Foreign Keys,Constraints,假设我有这些表: 创建表“account”( “id”整数主键 --更多领域 ); 创建表“产品”( “id”整数主键, “accountId”整数不为空, --更多领域 约束“fk_product_accountId”外键(“accountId”)在删除级联的更新级联上引用“account”(“id”) ); 创建表“标记”( “id”整数主键, “accountId”整数不为空, --更多领域 约束“fk_tag_accountId”外键(“accountId”)在删除级联的更新级联上引用“

假设我有这些表:

创建表“account”(
“id”整数主键
--更多领域
);
创建表“产品”(
“id”整数主键,
“accountId”整数不为空,
--更多领域
约束“fk_product_accountId”外键(“accountId”)在删除级联的更新级联上引用“account”(“id”)
);
创建表“标记”(
“id”整数主键,
“accountId”整数不为空,
--更多领域
约束“fk_tag_accountId”外键(“accountId”)在删除级联的更新级联上引用“account”(“id”)
);
创建表“产品标签”(
“productId”整数不为空,
“tagId”整数不为空,
主键(“productId”、“tagId”),
约束“fk_tag_productId”外键(“productId”)在删除级联的更新级联上引用“product”(“id”),
约束“fk_product_tagId”外键(“tagId”)在删除级联的更新级联上引用“标记”(“id”)
);
。。。换句话说,帐户可以有与帐户关联的产品和标记,然后可以将这些标记与这些产品关联

是否有一种方法可以在数据库级别定义一个约束,用于检查
产品标签
组合是否有效,即
标签
产品
是否都属于同一个帐户,这样我就可以避免在
插入
语句期间检查该有效性

我想在
product_tag
中增加一个列
accountId
,但我不认为我可以在这个列上定义多个外键,也不认为这实际上会以我想要的方式检查约束

CHECK
约束是否提供了这种程度的复杂性

Account:
   AccountID int not null

Tag:
   AccountID int not null
   TagID int not null
   primary key (AccountID, TagID)
   foreign key (AccountID) references Account(AccountID)

Product:
   AccountID int not null
   ProductID int not null
   primary key (AccountID, ProductID)
   foreign key (AccountID) references Account(AccountID)

ProductTag:
   AccountID int not null,
   TagID int not null,
   ProductID int not null,
   primary key(AccountID, TagID, ProductID)
   foreign key(AccountID, TagID) references Tag(AccountID, TagID)
   foreign key(AccountID, ProductID) references Product(AccountID, ProductID)
标签
产品
上使用复合键而不是身份键,使我们能够做到这一点。我发现使用标识键(代理键)可能会限制约束的有用性,因为表中包含的“信息”更“分散”,而且约束只能在一个或两个表上工作,不严格地说。我非常喜欢这个场景作为这种效果的一个例子。使用第一种设计会迫使我们使用触发器来执行规则


我很想看看其他人对此的解决方案…

外键约束只关心属于约束的列,因此检查帐户ID的唯一方法是使其成为所有键的一部分

检查约束不能包含子查询,因此它们不能用于此操作

使用现有数据库结构处理此问题的唯一方法是触发器:

CREATE TRIGGER check_product_tag_same_account
BEFORE INSERT ON product_tag
FOR EACH ROW
WHEN (SELECT accountId FROM product WHERE id = NEW.productId) !=
     (SELECT accountId FROM tag     WHERE id = NEW.tagId    )
BEGIN
    SELECT raise(FAIL, "account IDs do not match");
END;

这确实是我想要的方式。很不错的。当我第一次尝试不使用复合主键时,它确实失败了。我没有意识到这只适用于复合键。但是,正如您可能已经预料到的,我宁愿继续使用身份密钥。:-)回答得很好!但我也期待着其他的建议。同时,我将尝试您的解决方案的不同组合(可能是使用唯一键等),看看我是否仍然可以使用标识键(可能会失败:-)。定义
产品
标签
上的
唯一
约束似乎也确实有效!这允许我继续使用身份密钥,除非我忽略了这里的重要问题;我必须做更多的测试以确保我不是唯一的。确实,这些列允许是唯一的,但是如果您尝试将ProductID/TagID作为这些表中的主键(而不是Account、Product复合键),您将发现无法为复合非键列组合(AccountID、TagID)提供外键…至少在SQL Server中,对SQLite不太确定。这是一个奇怪的限制,因为如果ProductID是唯一的,那么(AccountID,ProductID)肯定是唯一的……我所做的是,在
product
tag
中保留标识键
id
,但还为这两个表定义了一个
唯一(id,AccountID)
复合约束。这使我能够在
产品标签
(您在回答中建议的)中创建所需的两个(重叠)外键,并且这确实按照我的预期方式强制执行约束。我不完全确定这是否是您评论它在SQL Server中不起作用的意思,但这个设置实际上在sqlite中起作用。再次感谢你的建议!很高兴我能让它工作。哦,想象一下,我没有想到——我不会说谎,我过去一直在努力解决这个确切的情况,我不得不解决复合键,因为我没有弄明白…哈哈。这种设计的酷之处在于,现在您可以让其他表具有FK to only ProductID,例如,如果它们不需要像ProductTag那样的AccountID。太好了。我也要试一试。它实际上看起来与我在考虑使用
CHECK
约束时模模糊糊想到的相似,同时不确定它们是否考虑到了这种复杂性。感谢您确认它们不存在,并感谢您使用触发器的替代解决方案。