数据库设计:规范化“(多对多)对多”关系

Mic*_*ood 14 database-design relational-theory

精简版

我必须在现有的多对多连接中为每对添加固定数量的附加属性。跳到下图,从优点和缺点来看,选项 1-4 中哪一个是通过扩展基本案例来实现这一目标的最佳方式?或者,有没有更好的选择我在这里没有考虑过?

更长的版本

我目前通过中间连接表有两个多对多关系的表。我现在需要添加指向属于这对现有对象的属性的附加链接。对于每一对,我有固定数量的这些属性,尽管属性表中的一个条目可能适用于多对(甚至对于一对可以多次使用)。我正在尝试确定执行此操作的最佳方法,并且无法理清如何看待这种情况。从语义上讲,我似乎可以将其描述为以下任何一项:

  1. 一对链接到一组固定数量的附加属性
  2. 一对链接到许多其他属性
  3. 许多(两个)对象链接到一组属性
  4. 许多对象链接到许多属性

例子

我有两种对象类型,X 和 Y,每个都有唯一的 ID,还有一个objx_objy带有列x_id和的链接表y_id,它们一起构成了链接的主键。每个 X 可以与许多 Y 相关,反之亦然。这是我现有的多对多关系的设置。

基本情况

基本情况

现在另外我在另一个表中定义了一组属性,以及一组条件,在这些条件下给定的 (X,Y) 对应该具有属性 P。条件的数量是固定的,所有对都相同。他们基本上说“在情况 C1 中,对 (X1,Y1) 具有属性 P1”,“在情况 C2 中,对 (X1,Y1) 具有属性 P2”,依此类推,对于连接中的每对的三种情况/条件桌子。

选项1

在我目前的状况正好有三个这样的条件,我也没有理由认为增加,所以一种可能性是添加列c1_p_idc2_p_id以及c3_p_idfeatx_featy,指定用于给定x_idy_id,其性能p_id在每个三种情况使用.

选项1

这对我来说似乎不是一个好主意,因为它使 SQL 复杂化以选择应用于功能的所有属性,并且不容易扩展到更多条件。但是,它确实强制要求每个 (X,Y) 对有一定数量的条件。事实上,这是这里唯一的选择。

选项 2

创建条件表cond,将条件ID添加到连接表的主键中。

选项 2

这样做的一个缺点是它没有指定每对条件的数量。另一个是,当我只考虑最初的关系时,例如

SELECT objx.*, objy.* FROM objx
  INNER JOIN objx_objy ON objx_objy.x_id = objx.id
  INNER JOIN objy ON objy.id = objx_objy.y_id
Run Code Online (Sandbox Code Playgroud)

然后我必须添加一个DISTINCT子句以避免重复条目。这似乎失去了每对应该只存在一次的事实。

选项 3

在连接表中创建一个新的“配对 ID”,然后在第一个与属性和条件之间创建第二个链接表。

选项 3

除了缺乏对每对执行固定数量的条件之外,这似乎具有最少的缺点。创建一个新的 ID 来识别除了现有 ID 之外的其他 ID 是否有意义?

选项 4 (3b)

与选项 3 基本相同,但没有创建附加 ID 字段。这是通过将两个原始 ID 放在新连接表中来实现的,因此它包含x_idy_id字段,而不是xy_id

选项 4

这种形式的另一个优点是它不会改变现有的表(尽管它们尚未投入生产)。但是,它基本上会多次复制整个表(或者感觉是这样),所以看起来也不理想。

概括

我的感觉是选项 3 和选项 4 足够相似,我可以选择任何一个。如果不是因为需要少量固定数量的属性链接,我现在可能会有,这使得选项 1 看起来比其他方式更合理。根据一些非常有限的测试,DISTINCT在我的查询中添加子句似乎不会影响这种情况下的性能,但我不确定选项 2 是否代表这种情况以及其他情况,因为放置导致的固有重复链接表的多行中的相同 (X,Y) 对。

这些选项之一是我最好的前进方式,还是我应该考虑另一种结构?

Jac*_*las 7

  • 选项1

    *这对我来说似乎不是一个好主意,因为它使 SQL 复杂化以选择应用于功能的所有属性......

    它不一定会使查询 SQL 复杂化(请参阅下面的结论)。

    ......并且不容易扩展到更多条件......

    它很容易扩展到更多条件,只要仍然有固定数量的条件,而不是几十个或数百个。

    但是,它确实强制要求每个 (X,Y) 对有一定数量的条件。事实上,这是这里唯一的选择。*

    确实如此,尽管您在评论中说这是“我的要求中最不重要的”,但您并没有说这根本不重要。

  • 选项 2

    这样做的一个缺点是它没有指定每对条件的数量。另一个是,当我只考虑初始关系时……我必须添加一个 DISTINCT 子句以避免重复条目……

    由于您提到的并发症,我认为您可以忽略此选项。该objx_objy表可能是您的某些查询的驱动表(例如“选择应用于某个特征的所有属性”,我认为这意味着应用于 anobjx或 的所有属性objy)。您可以使用视图来预先应用DISTINCT它,因此这不是使查询复杂化的问题,但这会在性能方面非常糟糕,而收益却很少。

  • 选项 3

    创建一个新的 ID 来识别除了现有 ID 之外的其他 ID 是否有意义?

    不,它没有 - 选项 4 在各方面都更好。

  • 选项 4

    ...它基本上多次复制整个表(或者感觉是这样),所以看起来也不理想。

    这个选项很好——如果属性的数量是可变的或可能会改变,它是建立关系的明显方法

结论

如果每个属性的数量objx_objy可能是稳定的,并且如果您无法想象添加超过少数的额外属性,我的首选将是选项 1 。它也是唯一一个强制执行“属性数量 = 3”约束的选项——对选项 4 强制执行类似的约束可能涉及向c1_p_idxy 表中添加……列*。

如果您真的不太关心该条件,并且您也有理由怀疑属性数量条件是否会稳定,则选择选项 4。

如果您不确定哪个,请选择选项 1 - 它更简单,如果您有选项,那肯定更好,正如其他人所说。如果您推迟选项 1“...因为它使 SQL 复杂化以选择应用于功能的所有属性...”我建议创建一个视图以提供与选项 4 中的额外表相同的数据:

选项 1 表:

create table prop(id integer primary key);
create table objx(id integer primary key);
create table objy(id integer primary key);

create table objx_objy(
  x_id integer references objx
, y_id integer references objy
, c1_p_id integer not null references prop
, c2_p_id integer not null references prop
, c3_p_id integer not null references prop
, primary key (x_id, y_id)
);

insert into prop(id) select generate_series(90,99);
insert into objx(id) select generate_series(10,12);
insert into objy(id) select generate_series(20,22);

insert into objx_objy(x_id,y_id,c1_p_id,c2_p_id,c3_p_id)
select objx.id, objy.id, 90, 91, 90+floor(random()*10)
from objx cross join objy;
Run Code Online (Sandbox Code Playgroud)

查看“模拟”选项 4:

create view objx_objy_prop as
select x_id
     , y_id
     , unnest(array[1,2,3]) c_id
     , unnest(array[c1_p_id,c2_p_id,c3_p_id]) p_id
from objx_objy;
Run Code Online (Sandbox Code Playgroud)

“选择应用于功能的所有属性”:

select distinct p_id from objx_objy_prop where x_id=10 order by p_id;

/*
|p_id|
|---:|
|  90|
|  91|
|  97|
|  98|
*/
Run Code Online (Sandbox Code Playgroud)

dbfiddle在这里