如何在2列上定义递归外键

时间:2013-08-20 13:01:25

标签: sql sql-server foreign-keys relational-database

我正在编写一个小应用来管理菜单的元数据。 Menu附加App,并且(可选)附加到另一个Menu(因此定义子菜单)。所以接线如下:将Menu.AppId附加到App.Id,然后Menu.ParentId附加到Menu.Id以定义子菜单。

但这可以让我插入不连贯的数据:

INSERT INTO Menu (Id, ParentId, AppId, Desc) values (1, NULL, 25, 'Top Menu')
INSERT INTO Menu (Id, ParentId, AppId, Desc) values (2, 1, 36, 'Sub Menu')

我刚才声明应用#36 子菜单应位于应用#25 热门菜单下>(另一个应用程序)。

我是否可以定义约束以确保当我将子菜单作为顶级菜单的子项插入时,该应用必须为#25(触发器不是一个选项)?

(当然我会在用户界面中管理这个,但我也在寻找一种方法来保护模型)。

谢谢,

2 个答案:

答案 0 :(得分:1)

您可以使用外键约束

第一个约束(Id, AppId)是唯一的(很明显,如果Id是PK,但需要检查以下FK约束):

alter table Menu add constraint unique_app unique (Id, AppId)

然后限制孩子拥有相同的AppId

alter table Menu add constraint fk_same_parent_app
foreign key (ParentId, AppId) references Menu(Id, AppId)

编辑:如果您需要更改AppId,则必须立即对层次结构中的每个菜单执行此操作。 这可以使用CTE进行递归查询来完成:

with AMenu(Id, ParentId)
as (
  -- start with a root
  select Id, ParentId
  from Menu where Id = <id_of_a_root>
union all 
  -- recursively add children
  select m.Id, m.ParentId
  from Menu m 
  join Amenu am on m.ParentId = am.Id
)
update m
set AppId = <some_value>
from Menu m 
join AMenu am 
on m.Id = am.Id

答案 1 :(得分:0)

好的我有办法,但它要求你让appid允许空值。在这种情况下,appid只能在父级中。

create table #temp (parentid int null,  appid int NUll)
ALTER TABLE #temp
ADD CONSTRAINT myconstraint CHECK (parentid+appid = Null and (isnull(parentid, 0)+isnull(appid,0) = parentid  or isnull(parentid, 0)+isnull(appid,0) = appid));

insert #temp
values(1,null)
insert #temp
values(null,1)
insert #temp
values(1,1)

我不知道那种结构对你有用,但这是我能看到提出工作检查约束的唯一方法。