如果您的 DBMS 支持这些(并且由此产生的性能是可以容忍的),您可以使用简单的模式和一般的数据库约束来做到这一点。不幸的是,大多数人都没有。
除此之外,最干净的方法是重叠外(超级)键。这是一种不需要 NULL 的伪代码关系方法;省略数据类型和无趣的属性:
create table Task {
TaskId,
key { TaskId }
};
create table Owner {
OwnerId,
key { OwnerId }
};
create table TaskOwner { /* Bog standard association table */
TaskId,
OwnerId,
key { Taskid, OwnerId }
reference TO_T { TaskId } references Task { TaskId },
reference TO_O { OwnerId } references Owner { OwnerId }
};
create table TaskGroup {
GroupId,
OwnerId,
key { GroupId }, /* Each group has exactly one owner */
reference TG_O { OwnerId } references Owner { OwnerId }
};
create table TaskGroupTask {
TaskId,
GroupId,
OwnerId,
key { TaskId }, /* Each task may belong to at most one group */
reference TGT_TO { TaskId, OwnerId } references TaskOwner { TaskId, OwnerId },
/* Foreign superkey coming up */
reference TGT_TG { GroupId, OwnerId } references TaskGroup { Group, OwnerId }
};
线索是TaskGroupTask.Ownerid 作为对TaskOwner 和TaskGroup 的引用的一部分执行双重职责,从而确保组的所有者与其中每个任务的所有者相同。这里有一个外来的超键——TGT_TG 指的是TaskGroup 键的超集——但如果您的 DBMS 反对这一点,一个可能的解决方法是在超集上创建一个(多余的)唯一索引。
拥有一个单独的TaskGroupTask 表而不是一个可以为空的TaskOwner.GroupId 属性乍一看似乎有点矫枉过正,但它允许您使用简单的键约束来强制执行任务最多属于一个组的规则.此外,它还避免了 NULL。
TaskGroupTask中有一个函数依赖{ GroupId } -> { OwnerId }违反了Boyce-Codd范式,但是冗余是由TGT_TGreference控制的,需要强制执行情况规则。
更改组的所有者(及其所有任务)涉及在同一语句/事务中更新三个表,但适当的 RDBMS 应该没有问题。
这种设计确实允许一个任务属于多个所有者并且同时在一个组中,创造了一种情况,一个组中的所有任务都属于同一个所有者,但它们属于其他所有者(尽管不是组)也是如此。从你的问题中不清楚这是否是一个问题,所以如果是这样,请告诉我。该设计还不允许没有所有者的组,但我认为这没问题。