数据库设计:同一张表具有两个一对多关系


20

我必须对一种情况建模,即我有一个表Chequing_Account(包含预算,IBAN号和其他帐户详细信息),该表必须与两个不同的表Person和Corporation都可以具有0、1或许多支票帐户。

换句话说,我在同一个表中有两个一对多关系

我想听听尊重标准化要求的解决方案。我听到的大多数解决方案是:

1)找到一个人和公司都属于的公共实体,并在此实体和Chequing_Account表之间创建一个链接表,在我的情况下这是不可能的,即使我要解决一般问题而不是此特定实例也是如此。

2)创建两个链接表PersonToChequingAccount和CorporationToChequingAccount,它们将两个实体与Chequing Accounts关联起来。但是,我不希望两个人拥有相同的支票帐户,也不想让自然人和公司共享一个支票帐户!看到这张图片

http://i41.tinypic.com/35i6kbk.png

3)在Chequing Account中创建两个指向公司和自然人的外键,但是我要强制一个人和Company可以有许多支票帐户,但是我必须手动确保对于每个ChequingAccount行,不是两个关系都指向公司和自然人,因为支票帐户是公司还是自然人。看到这张图片

http://i40.tinypic.com/1rpv9z.png

是否有其他更清洁的解决方案来解决此问题?


您是否考虑过OwnerTypeIDChecquingAccount表中使用1=Corporation2=NaturalPerson?这样一来,您只需OwnerIDChecquingAccount表格中添加一个即可将其与一起建立索引OwnerTypeID
RoKa 2013年

我不仅需要知道它是公司法人还是自然人,而且还需要知道各自的ID,所以我需要一个ID号,而不仅是1或2的值!解决方案3是我在这里发现的两列ID,分别代表公司或自然人
dendini 2013年

2
是的,解决方案是有效的选择。在大多数DBMS中,您可以强制两个FK中只有一个处于“活动”状态并带有检查约束:CHECK (CorporationID IS NOT NULL AND NaturalPersonID IS NULL OR CorporationID IS NULL AND NaturalPersonID IS NOT NULL)尽管如此,我还是更喜欢解决方案1(但这就是我自己)。它“干净”得多。
ypercubeᵀᴹ

是的,我了解,但是您可能在ChecquingAccount表中有OwnerTypeID=1和的记录OwnerID=123,表明它是type Corporation,因此123Corporation表中的ID 。OwnerTypeID告诉您哪个表,而OwnerID告诉您该表中的ID。
RoKa 2013年

1
选项#1如何不可能?毕竟,“公司”一词基本上是指“合法上是个人的业务”。称它为Customers桌子。
所有行业的乔恩

Answers:


15

关系数据库的建立不能完美地处理这种情况。您必须决定什么对您最重要,然后进行权衡。您有几个目标:

  • 保持第三范式
  • 保持参照完整性
  • 保持每个帐户属于公司或自然人的约束。
  • 保留简单直接地检索数据的能力

问题在于,其中一些目标相互竞争。

子类型解决方案
您可以选择子类型解决方案,在其中创建包含公司和个人的超级类型。此超类型可能具有子类型的自然键的复合键以及分区属性(例如customer_type)。就规范化而言,这很好,它允许您强制执行参照完整性,以及公司和人员相互排斥的约束。问题在于,这使数据检索更加困难,因为您始终必须根据customer_type将帐户加入帐户持有人的时间进行分支。这可能意味着UNION在查询中使用并包含很多重复的SQL。

两个外键解决方案
您可以选择一种解决方案,在帐户表中保留两个外键,一个给公司,一个给人。该解决方案还使您可以维护参照完整性,规范化和互斥。它还具有与子类型解决方案相同的数据检索缺点。实际上,此解决方案与子类型解决方案一样,只是您遇到了分支联接逻辑“很快”的问题。

但是,由于强制执行互斥约束,许多数据建模人员会认为此解决方案不如子类型解决方案。在子类型解决方案中,您使用键来强制相互排斥。在两个外键解决方案中,您使用CHECK约束。我知道有些人对支票约束有不公正的偏见。这些人会更喜欢将约束保持在键中的解决方案。

“非规范化”分区属性解决方案
还有另一种选择,您可以在支票帐户表上保留一个外键列,并使用另一列来告诉您如何解释外键列(RoKa的OwnerTypeID柱)。通过对子表的分区属性进行非规范化,这实质上消除了子类型解决方案中的超类型表。(请注意,根据正式定义,这并不是严格意义上的“非规范化”,因为partitioning属性是主键的一部分。)此解决方案看起来非常简单,因为它避免了使用额外的表来做或多或少地做相同的事情。将外键列的数量减少到一。该解决方案的问题在于,它不能避免检索逻辑的分支,而且不能允许您维护声明性引用完整性。SQL数据库无法管理用于多个父表之一的单个外键列。

共享主键域解决方案
人们有时处理此问题的一种方式是使用一个ID池,这样就不会混淆给定ID属于一个子类型还是另一个子类型。在银行业务场景中,这可能很自然,因为您不会向公司和自然人发行相同的银行帐号。这具有避免需要分区属性的优点。无论有没有超类型表,都可以这样做。使用超类型表允许您使用声明性约束来强制唯一性。否则,必须在程序上强制执行。此解决方案已标准化,但是除非您保留超类型表,否则它不允许您维护声明性引用完整性。它仍然无法避免复杂的检索逻辑。

因此,您可以看到,实际上不可能有一个遵循所有规则的简洁设计,同时又要保持数据检索的简单性。您必须决定要在哪里进行权衡。


我的解决方案#2属于四个小组中的哪个小组?在“非正规化分区属性解决方案”是不是很清楚,我..
dendini

@dendini-您的解决方案编号2不符合我概述的任何解决方案。这是因为它不符合属于一个法人实体的帐户的要求。定义中间表的主键的方式是多对多交集。如果主键是正义的 corporation_idperson_id那么从本质上讲您将拥有子键入解决方案,只是超类型表将被拆分为两个并且外键将被反转,因此人们无法拥有多个帐户。这种失败的目的。
乔尔·布朗

很好的解释。@JoelBrown,就查询而言,“两个外键”解决方案的性能含义是什么?另外,考虑到不是2,可能有6个或更多的外键:您是否仍然推荐这种方法,还是倾向于另一种方法?
Amadeo Gallardo 2015年

1
@AmadeoGallardo答案是“取决于”。对键的查询总是非常高效的,因为您通常可以指望至少进行索引扫描(如果不是查找的话),并且这是快速的操作。当您在两个外键解决方案中的两个键之间进行查询时,就会出现问题。在这里,您要求查询优化器执行“或”操作。最好情况下,这将使查询的成本增加一倍,通常会稍差一些,因为您必须先针对一个键进行查询,然后针对另一个键进行查询,然后合并结果。
乔尔·布朗

@JoelBrown非规范化的将来的SQL版本应该允许这种方法,方法是允许基于两列定义复合外键RefIDRefTable其中RefTable固定ID标识目标表。这种类型的键有很多用例,要维护10个或更多的关联/子类型表以增强完整性,要用到很多。对于那些情况,我key自己创建了这个。
djmj
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.