具有层次结构的表:创建约束以防止通过外键循环

Jer*_*oen 10 sql-server-2008 constraint hierarchy

假设我们有一个对自身有外键约束的表,如下所示:

CREATE TABLE Foo 
    (FooId BIGINT PRIMARY KEY,
     ParentFooId BIGINT,
     FOREIGN KEY([ParentFooId]) REFERENCES Foo ([FooId]) )

INSERT INTO Foo (FooId, ParentFooId) 
VALUES (1, NULL), (2, 1), (3, 2)

UPDATE Foo SET ParentFooId = 3 WHERE FooId = 1
Run Code Online (Sandbox Code Playgroud)

该表将有以下记录:

FooId  ParentFooId
-----  -----------
1      3
2      1
3      2
Run Code Online (Sandbox Code Playgroud)

在某些情况下,这种设计可能有意义(例如典型的“员工-老板-员工”关系),并且无论如何:我处于我的模式中有这种情况的情况。

不幸的是,这种设计允许数据记录中的循环,如上例所示。

那么我的问题是:

  1. 是否可以编写一个约束来检查这个?和
  2. 编写一个检查这个的约束是否可行?(如果只需要一定深度)

对于这个问题的第 (2) 部分,可能需要提及的是,我希望我的表中只有数百或在某些情况下可能有数千条记录,通常嵌套的深度不会超过大约 5 到 10 级。

附注。微软 SQL Server 2008


2012 年 3 月 14 日更新
有几个很好的答案。我现在已经接受了帮助我理解提到的可能性/可行性的那个。不过,还有其他几个很好的答案,其中一些还提供了实施建议,因此,如果您带着相同的问题来到这里,请查看所有答案;)

小智 7

我已经看到了两种主要的强制执行方式:

1、OLD方式:

CREATE TABLE Foo 
    (FooId BIGINT PRIMARY KEY,
     ParentFooId BIGINT,
     FooHierarchy VARCHAR(256),
     FOREIGN KEY([ParentFooId]) REFERENCES Foo ([FooId]) )
Run Code Online (Sandbox Code Playgroud)

FooHierarchy 列将包含如下值:

"|1|27|425"
Run Code Online (Sandbox Code Playgroud)

数字映射到 FooId 列的位置。然后,您将强制 Hierarchy 列以“|id”结尾,并且字符串的其余部分与 PARENT 的 FooHieratchy 匹配。

2、NEW方式:

SQL Server 2008 有一个名为HierarchyID的新数据类型,它为您完成所有这些工作。

它的操作原理与 OLD 方式相同,但它由 SQL Server 有效处理,适合用作“ParentID”列的替换。

CREATE TABLE Foo 
    (FooId BIGINT PRIMARY KEY,
     FooHierarchy HIERARCHYID )
Run Code Online (Sandbox Code Playgroud)


A-K*_*A-K 6

这是可能的:您可以从 CHECK 约束调用标量 UDF,它可以检测任何长度的循环。不幸的是,这种方法极其缓慢且不可靠:您可能会出现误报和漏报。

相反,我会使用物化路径。

另一种避免循环的方法是使用 CHECK(ID > ParentID),这也可能不太可行。

另一种避免循环的方法是再添加两列,LevelInHierarchy 和 ParentLevelInHierarchy,有 (ParentID, ParentLevelInHierarchy) 引用 (ID, LevelInHierarchy),并有一个 CHECK(LevelInHierarchy > ParentLevelInHierarchy)。


ype*_*eᵀᴹ 6

您正在使用邻接列表模型,在这种模型中很难强制执行此类约束。

您可以检查嵌套集模型,其中只能表示真正的层次结构(没有圆形路径)。但是,这还有其他缺点,例如缓慢的插入/更新。