首页 > 解决方案 > 在关系数据库中实现 1 到 0..1 关系的正确方法

问题描述

想象一下,在我们的数据模型中,我们有一个实体(一种数据结构),它具有可选部分。我们可以将这些“部分”实现为对其他(子)实体的可空引用。换句话说,主实体的每个实例可能具有或不具有与其关联的其他(子)实体的单个实例,并且子实体的任何实例仅具有与其关联的主实体的一个实例。所以我们有 1 到 0..1 的关系。

例如,审计日志记录有共同的字段,如(时间戳、用户、操作),也有特定于操作的部分(扩展信息),对于不同的操作可能完全不同。我们可以使用单独的实体来表示每种类型的扩展信息,然后使主实体具有对每种可能类型的扩展信息的可空引用。

我可以看到在关系数据库中实现它的两种方法:

1. 对主记录中子实体的可空引用

对于每种类型的子实体,主记录表都有一个字段,该字段引用子(扩展)表中记录的 ID 作为外键。

这似乎是更直接的选择:要检索相关信息,我们只需遵循直接引用即可。在 SQL 查询中,我们将通过外键左连接子(扩展)表。外键的空值将为我们提供所有子表字段的空值。

2.子实体的记录参考主实体的记录

我们不在主实体表中存储任何引用。相反,子实体表的每条记录都通过 ID 引用主表中的一条记录作为外键。在 SQL 查询中,我们仍然将子表左连接到主表,并且对于所有没有对应子记录的子表的字段,我们将获得空值。

???

哪种方法是正确的?第二个似乎更具关系性,我们不必在主表中创建额外的字段,但从技术上讲,查找相关记录可能需要更多的工作,因为我们必须在子表。或者数据库引擎优化这种连接是快速的,例如使用索引?索引搜索比扫描快,但仍然比直接引用慢。Plus 索引占用空间。我填补了数据库引擎如何工作的知识不足......或者我只是错过了一些明显的东西。帮助将不胜感激。

更新

在得到下面的答案后,也有了更多的思考,决定使用第二种方法。除了在接受的答案中所说的(更紧凑,从关系的角度来看更正确,不必处理 NULL),如果我需要删除所有对应的主记录,它还为我提供了使用级联删除的很好的可能性子记录。

标签: database-designrelational-databasedatabase-normalization

解决方案


在这种情况下,我认为“最佳”解决方案取决于数据库的工作负载,因为这两种解决方案都有优点和缺点。

第二种解决方案没有空值,这通常会使查询复杂化(空值也使解决方案对于关系模型的纯粹主义者来说不“正确”),但还有其他好处:它需要更少的空间(以便关系对于某些类型的操作,更紧凑并且需要更少的时间来操作)。另一方面,它需要连接才能访问详细数据(因此这些操作需要额外的索引)。

第一个解决方案在概念上更简单,在访问详细数据的情况下不需要连接,但需要更多空间,这会减慢某些类型的操作。

这两种解决方案都在实际环境中使用。

我认为,只有知道必须在此类数据库上部署的应用程序的典型工作负载是什么,才能为这种困境提供解决方案:某些查询相对于其他查询是否更频繁(或必须具有更少的延迟)?例如,只查看一般数据的查询比需要详细数据的查询更常用?

最后,如果很难或不可能进行这种“理论”分析,唯一的另一种方法是尝试一种解决方案,但如果性能不令人满意,则准备尝试另一种解决方案。这可以通过使用视图来完成,例如以这种方式:

  1. 从第二个解决方案开始,并定义一个执行连接的视图。

  2. 在适当的时候使用基表或连接视图来编写应用程序。

  3. 如果性能不令人满意,则通过连接创建一个与旧视图具有相同名称的新表来切换到其他解决方案,并定义一个仅对非空属性执行投影的新视图。

通过在这两种情况下为视图和基表交换相同的名称,应用程序将只需要进行最少的修改,您可以对这两种方法进行试验。


推荐阅读