关于数据库:在SQL中处理一对一关系的最佳方法是什么?

关于数据库:在SQL中处理一对一关系的最佳方法是什么?

What's the best way to handle one-to-one relationships in SQL?

假设我有一些Alpha事物可能与Bravo或Charlie事物相关或无关。

这些是一对一的关系:没有Alpha会涉及不止一个Bravo。而且,Bravo不会与一个以上的Alpha相关。

我有一些目标:

  • 一个易于学习的系统
    保持。
  • 我内部实施的数据完整性
    数据库。

  • 现实世界的逻辑组织
    我的数据。
  • 我内的类/对象
    很好地映射到
    数据库表(从Linq到SQL)
  • 快速的读写操作
  • 有效利用空间(少数空字段)

我有三个主意

1
2
3
PK = primary key  
FK = foreign key  
NU = nullable

一个包含许多nullalbe字段的表(平面文件)

1
2
3
4
5
6
7
8
9
10
11
12
      Alphas
      --------
   PK AlphaId
      AlphaOne
      AlphaTwo
      AlphaThree
   NU BravoOne
   NU BravoTwo
   NU BravoThree
   NU CharlieOne
   NU CharlieTwo
   NU CharlieThree

许多表的零nullalbe字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
      Alphas
      --------
   PK AlphaId
      AlphaOne
      AlphaTwo
      AlphaThree

      Bravos
      --------
FK PK AlphaId
      BravoOne
      BravoTwo
      BravoThree

      Charlies
      --------
FK PK AlphaId
      CharlieOne
      CharlieTwo
      CharlieThree

两者的最佳(或最差):许多表都有很多nullal外键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
      Alphas
      --------
   PK AlphaId
      AlphaOne
      AlphaTwo
      AlphaThree
NU FK BravoId
NU FK CharlieId

      Bravos
      --------
   PK BravoId
      BravoOne
      BravoTwo
      BravoThree

      Charlies
      --------
   PK CharlieId
      CharlieOne
      CharlieTwo
      CharlieThree

如果Alpha必须是Bravo或Charlie,但不能同时是两者,该怎么办?

如果Alphas不仅可以是Bravos和Charlies,还可以是Delta,Echos,Foxtrots或Golfs等中的任何一个?

编辑:这是问题的一部分:哪个是我的导航的最佳数据库架构?


如果您希望每个Alpha仅通过一个Bravo关联,那么我会结合使用FK / PK投票赞成这种可能性:

1
2
3
4
5
6
      Bravos
      --------
FK PK AlphaId
      BravoOne
      BravoTwo
      BravoThree

这样,只有一个Bravo可以引用您的Alpha。

如果Bravos和Charlies必须互斥,则最简单的方法可能是创建一个discriminator字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
      Alpha
      --------
   PK AlphaId
   PK AlphaType NOT NULL IN ("Bravo","Charlie")
      AlphaOne
      AlphaTwo
      AlphaThree

      Bravos
      --------
FK PK AlphaId
FK PK AlphaType =="Bravo"
      BravoOne
      BravoTwo
      BravoThree

      Charlies
      --------
FK PK AlphaId
FK PK AlphaType =="Charlie"
      CharlieOne
      CharlieTwo
      CharlieThree

这样,AlphaType字段将强制记录始终完全属于一个子类型。


我假设您将使用SQL Server 2000/2005。我有一个用于我与我之间一对一关系的标准模式,这与您的第二个想法并不太相似,但是这里有区别:

  • 每个实体都必须首先拥有自己的主键,因此,除了Alpha表的外键列之外,您的Bravo,Charlie等表还应定义自己的代理键。通过指定一个表的主键必须与另一个表的主键完全相同,使域模型变得非常不灵活。因此,实体变得非常紧密地耦合在一起,一个实体不能不存在另一个实体,这不是需要在数据库设计中强制执行的业务规则。

  • 在Bravo和Charlie表的AlphaID列之间向Alpha表的主键列添加外键约束。这使您可以一对多进行操作,还可以通过设置FK列的可空性(在当前设计中无法实现)来指定关系是否是强制性的。

  • 在表AlvoID列的Bravo,Charlie等上添加唯一的键约束。这将创建一对一的关系,其附加好处是唯一键还可以用作索引,这可以帮助加快基于外键值检索行的查询。

这种方法的主要好处是更改变得更容易:

  • 想要一对多吗?删除相关的唯一键,或仅将其更改为普通索引
  • 想要Bravo独立于Alpha生存吗?您已经有了代理键,您要做的就是将AlphaID FK列设置为允许NULL


就个人而言,我在您的第二个模型上取得了很多成功,在单个列上使用了PK / FK。

我从来没有遇到过所有Alpha都必须在Bravo或Charlie表中有记录的情况。我一直处理1 <-> 0..1,从不处理1 <-> 1。

至于最后一个问题,那就是更多的表。


您提出了很多问题,这使得很难选择任何建议的解决方案,而没有对正在尝试解决的确切问题进行更多的澄清。不仅要考虑我的澄清问题,还要考虑评估我的问题所使用的标准,以表明解决问题所需的细节量:

  • 一个易于学习和维护的系统。

易于学习和维护的"系统"是什么?您的应用程序的源代码还是通过其最终用户界面的应用程序数据?

  • 在我的数据库中强制执行数据完整性。

"在数据库中强制执行"是什么意思?这是否意味着您不能以任何其他方式控制数据完整性,即项目仅需要基于DB的数据完整性规则?

  • 一种与我的数据的真实逻辑组织相匹配的架构。

您能为我们提供您所指的现实世界,逻辑组织吗?从您要存储的数据的三个示例中不可能推断出这一点-即假设所有三个结构都是完全错误的。除非我们了解实际规格,否则我们怎么知道?

  • 我编程中的类/对象可以很好地映射到数据库表(Linq to SQL)

这个要求听起来像是您的手被迫使用linq to SQL创建它,是这样吗?

  • 快速的读写操作

什么是"快速"? .03秒? 3秒? 30分钟?尚不清楚,因为您没有指定数据大小和要引用的操作类型。

  • 有效利用空间(少数空字段)

有效使用空间与空字段的数量无关。如果您指的是规范化的数据库结构,那将再次取决于问题中未提供的真实规范和应用程序的其他设计元素。


到目前为止,我有一个可以很好地适合您的模型的示例:

我有具有Alpha外键alpha_id的Charlie和Bravo表。像您的第一个示例一样,除了alpha不是主键,bravo_id和charlie_id是。

我在需要寻址到这些实体的每个表上使用alpha_id,因此,为了避免可能会导致在研究Bravo和Charlie来查找哪个Alpha时出现延迟的SQL,我创建了一个AlphaType表,并在Alpha表上拥有其ID (alpha_type_id)作为外键。这样,我可以以编程方式知道我要处理的AlphaType,而无需联接可能有成千上万条记录的表。在tSQL中:

1
2
3
4
5
6
7
8
9
10
11
// For example sake lets think Id as a CHAR.
// and pardon me on any mistake, I dont have the exact code here,
// but you can get the idea

SELECT
  (CASE alpha_type_id
    WHEN 'B' THEN '[Bravo].[Name]'
    WHEN 'C' THEN '[Charlie].[Name]'
    ELSE Null
  END)
FROM ...

您可能有一个连接表,用于指定Alpha和相关ID。然后,您可以添加另一列,以指定它是Bravo,Charlie或其他名称的ID。使列保持在Alpha上,但确实增加了连接查询的复杂性。


另一种方法是使用3个表来存储3个实体,并使用一个单独的表来存储关系。


我将创建一个超类型/子类型关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
   THINGS
   ------
PK ThingId  

   ALPHAS
   ------
FK ThingId (not null, identifying, exported from THINGS)
   AlphaCol1
   AlphaCol2
   AlphaCol3  

   BRAVOS
   ------
FK ThingId (not null, identifying, exported from THINGS)
   BravoCol1
   BravoCol2
   BravoCol3  

   CHARLIES
   --------
FK ThingId (not null, identifying, exported from THINGS)
   CharlieCol1
   CharlieCol2
   CharlieCol3

因此,例如,一个具有查理但没有勇气的字母:-

1
2
3
insert into things values (1);
insert into alphas values (1,'alpha col 1',5,'blue');
insert into charlies values (1,'charlie col 1',17,'Y');

请注意,您不能为alpha创建多个charlie,就像您尝试创建ThingId为1的两个charlies一样,第二个插入将获得唯一的索引/约束冲突。


除非我有重大理由不这样做,否则我会选择选项1。尤其是,它可能不会花费您想象中那么多的空间。如果您在Bravo中使用varchars。不要忘记,拆分将花费您外键,二级身份和所需的索引。

您可能会遇到麻烦的地方是,如果不太可能需要Bravo(<%10),并且需要按其字段之一快速查询,以便对其进行索引。


推荐阅读