【问题标题】:How to define recursive foreign key on 2 columns如何在 2 列上定义递归外键
【发布时间】:2013-08-20 13:01:25
【问题描述】:

我正在编写一个小应用程序来管理菜单的元数据。一个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应用的Sub Menu应该在#25应用的Top Menu之下>(另一个应用程序)。

有没有一种方法可以定义一个约束,以确保当我将 Sub Menu 作为 Top Menu 的子项插入时,应用程序必须是 #25(触发器不是一个选项)?

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

谢谢,

【问题讨论】:

  • 为什么不能选择触发器?
  • @HLGEM 我正在​​寻找我可能错过的 RDBMS 提供的内置功能。我想在使用触发器之前了解所有可能的选项。

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


【解决方案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

【讨论】:

  • 这看起来不错,但是一旦我将孩子添加到父母中,我就不能再更改它的 AppId(当然这是自然的)。有什么建议可以解决这个问题?
【解决方案2】:

好的,我有一个办法,但它需要你让 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)

我不知道这种结构是否适合您,但这是我能想到的唯一一种提出有效检查约束的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-20
    • 2020-09-27
    • 2021-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-06
    相关资源
    最近更新 更多