相互反向引用的一对多关系的最佳设计是什么?

What is best design for one-to-many relationship with back references to each other?

我正在尝试为 SQL 一对多关系的数据库架构寻找最佳设计。在我的项目中,我有 objects,它由 nodes 组成,我希望每个 object 都有对 root_node 的可选外键引用。所以我最初的解决方案是这样的(为了清楚起见,我跳过了依赖问题):

-- schema A

CREATE TABLE objects (
   object_id integer NOT NULL PRIMARY KEY,
   root_node integer REFERENCES nodes(node_id),
    <some other data>
);

CREATE TABLE nodes (
   node_id integer NOT NULL PRIMARY KEY,
   object_id integer REFERENCES objects,
   <some other data>
);

但是现在我们有两个 table 相互引用了外键,我不确定这是不是一件好事。所以我正在考虑另一种方法,而不是将 root_node 放在 objects table 中,而是单独存储为 root_nodes:

-- schema B

CREATE TABLE objects (
   object_id integer NOT NULL PRIMARY KEY,
    <some other data>
);

CREATE TABLE root_nodes (
   object_id integer REFERENCES objects PRIMARY KEY,
   root_node integer REFERENCES nodes(node_id),
);

CREATE TABLE nodes (
   node_id integer NOT NULL PRIMARY KEY,
   object_id integer REFERENCES objects,
   <some other data>
);

所以我的问题是:AB 设计是否都被认为是可接受的table 或者有一个已知的 'best practice' 会更喜欢一个而不是另一个?如果是这样,您能否提供其中一种模式更好的理由?

在这种情况下,您正在对您的数据强制执行一个额外的约束,您没有让数据库知道您的 table 定义。
您需要添加一个触发器来强制执行约束,或者您完全忽略根而不是信息并在需要时以编程方式找到它。

您当前方案的最大问题是您可以更改一个 table 中的信息,从而导致另一个中的信息语义错误。

例如:

A -> B -> C  
A is the root node of C  

我可以通过更新一个 table 中的某些内容但在另一种格式中忽略这样做来打破你的任何一种格式。我可以将C的根节点更新为B,但忘记删除A和B之间的父子关系。或者我可以为A添加一个父节点,而忘记更新B或C的根节点信息。

我的建议是不要存储根节点数据并在需要时计算它。

在模式B中,对象可以有多个根节点,根节点可以是另一个对象的节点。模式 A 最多为对象强制一个根节点(我猜这是我们想要的),但共享第二个问题。我不知道是否有一些"best practice",但这里有一些想法。

如果对象需要更多的根节点,其实很简单,只需要位标志:

CREATE TABLE objects (
   object_id integer NOT NULL PRIMARY KEY,   
    <some other data>
);

CREATE TABLE nodes (
   node_id integer NOT NULL PRIMARY KEY,
   object_id integer REFERENCES objects,
   is_root bit NOT NULL
   <some other data>
);

如果对象只需要一个根节点,可以添加过滤唯一索引:

CREATE UNIQUE NONCLUSTERED INDEX unique_root_for_object ON nodes
(
    object_id
)
WHERE (is_root = 1)

我们暂时称它为模式 C。现在让 return 到模式 A 并修复 "root from different object" 问题。您可以添加复合外键以强制根节点成为对象节点之一:

ALTER TABLE objects WITH CHECK CHECK 
CONSTRAINT FK_objects_nodes FOREIGN KEY(object_id, root_node) 
REFERENCES nodes (object_id, node_id)

您需要 table 节点上的 (object_id, node_id) 上的唯一索引才能工作。当然你仍然可以有没有根节点的对象,它们不会违反这个外键。

方案A好还是方案C好?模式 C 似乎更灵活,例如,您可以在一个插入中添加节点作为根节点。您也可以轻松地将其切换到 "multiple root nodes" 场景。另一方面,模式 A 允许您在具有根节点信息的对象上创建索引。记录更改时,根节点的更改将记录为对象的更改,而不是节点的更改。依赖性更明确,这会稍微简化一些查询,ORM 也会喜欢它。

可能还有其他方法可以做到这一点。根据经验,我会尽量坚持设计时不允许数据不一致的模式。