如何在 SQL 表中最好地实施单级递归?
-
05-07-2019 - |
题
假设您的组织中有一个分支机构表。其中一些是“主要”分支机构,另一些是汇总到主要分支机构的卫星办公室。除了这种区别只影响系统中的一些事情之外,分支都是对等的并且具有相同的属性(地址等)。对此进行建模的一种方法是使用如下表:
CREATE TABLE Branch (
branch_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
branch_name VARCHAR(80) NOT NULL,
street VARCHAR(80) NULL,
city VARCHAR(30) NULL,
state CHAR(2) NULL,
zip CHAR(5) NULL,
is_satellite_office BIT NOT NULL DEFAULT(0),
satellite_to_branch_id INT NULL REFERENCES Branch(branch_id)
)
在哪里 is_satellite_office
= 1 当且仅当此记录是另一个分支的卫星,并且 satellite_to_branch_id
指的是您属于哪个分支(如果有)。
在表上设置约束以使这两列在任何给定记录上一致是很容易的:
CONSTRAINT [CK_Branch] CHECK
(
(is_satellite_office = 0 AND satellite_to_branch_id IS NULL)
OR (is_satellite_office = 1 AND satellite_to_branch_id IS NOT NULL)
)
然而,我真正想要的是一种保证这种递归只进行的方法 一 水平深...也就是说,如果我指向一个分支作为我的父级,它本身一定不能有父级,并且它的值 is_satellite_office
必须为 0。换句话说,我并不真正想要一个完全递归的树结构,我只是想将其限制为单个父/子关系。这就是我要编写代码的方式,如果有一种方法可以在数据库中强制执行它,并且不会像完全垃圾一样执行,我愿意。
有任何想法吗?我正在研究 MSSQL 2005,但首选通用(非特定于供应商)解决方案。并且不需要应用任何触发器,除非确实没有其他方法可以做到这一点。
编辑:需要明确的是, satellite_to_branch_id
是指向同一分支表中另一条记录的递归指针。我知道我可以删除 is_satellite_office BIT
并依靠 IsNull(satellite_to_branch_id)
给我相同的信息,但我发现明确一点更清楚,除此之外,这不是问题的要点。我真的在寻找一种纯粹的 SQL 约束方法来防止递归深度大于 1。
解决方案
您可以将检查约束绑定到UDF的返回值。创建一个UDF,将所涉及的列作为输入参数,然后使用UDF中的select检查所需的状态。
其他提示
对我而言似乎是业务约束,难以在数据定义级别强制执行。我不相信关系代数有任何支持来确定自引用深度的限制。
您是否可以在约束中引用存储过程?你可以在PostgreSQL中,所以如果2005年不允许的话,我会感到惊讶。
这个稍微不同的结构怎么样?
CREATE TABLE Branch (
branch_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
branch_name VARCHAR(80) NOT NULL,
street VARCHAR(80) NULL,
city VARCHAR(30) NULL,
state CHAR(2) NULL,
zip CHAR(5) NULL,
parent_id int NULL
)
PARENT_ID 将简单地指向同一表中另一条记录的 BRANCH_ID。如果它为空,那么你就知道它没有父母。
然后,要获得一级递归,您只需将表与其自身连接一次,如下所示:
SELECT
PARENT.BRANCH_NAME AS PARENT_BRANCH
,CHILD.BRANCH_NAME AS CHILD_BRANCH
FROM
BRANCH PARENT
,BRANCH CHILD
WHERE CHILD.PARENT_ID PARENT.BRANCH_ID
如果您想在树中强制执行一层深度,请创建一个插入/更新触发器,如果此查询返回任何内容,该触发器将引发异常。
SELECT *
FROM
BRANCH B1
,BRANCH B2
,BRANCH B3
WHERE B1.PARENT_ID = :NEW.NEW_PARENT_ID
AND B2.PARENT_ID = B1.BRANCH_ID
AND B2.PARENT_ID = B3.BRANCH_ID;