如何在SQL表中最好地强制执行单级递归?

时间:2009-01-08 16:29:06

标签: sql recursion constraints

假设您的组织中有分支表。其中一些是“主要”分支机构,另一些是附属于主要分支机构的卫星办公室。除了这种仅影响系统中的一些事物的区别之外,分支都是对等的并且具有相同的属性(地址等)。建模的一种方法是在表格中:

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。

4 个答案:

答案 0 :(得分:1)

对我而言似乎是业务约束,难以在数据定义级别强制执行。我不相信关系代数有任何支持来确定自引用深度的限制。

答案 1 :(得分:1)

您是否可以在约束中引用存储过程?你可以在PostgreSQL中,所以如果2005年不允许的话,我会感到惊讶。

答案 2 :(得分:1)

您可以将检查约束绑定到UDF的返回值。创建一个UDF,将所涉及的列作为输入参数,然后使用UDF中的select检查所需的状态。

答案 3 :(得分:0)

这个略有不同的结构怎么样?

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。如果它为null,那么你知道它没有父母。

然后,要获得一个级别的递归,您可以将表连接到自己一次,如下所示:

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;