在关系数据库中实现 1 到 0..1 关系的正确方法
Correct way to implement 1 to 0..1 relation in relational databases
想象一下,在我们的数据模型中,我们有一个实体(数据结构),它有可选的部分。我们可以将这些 "parts" 实现为对其他(子)实体的可为空的引用。换句话说,主实体的每个实例可能有或没有其他(子)实体的单个实例与之关联,而子实体的任何实例只有一个主实体实例与之关联。所以我们有 1 到 0..1 的关系。
例如,审计日志记录有公共字段(时间戳、用户、操作)和操作特定部分(扩展信息),对于不同的操作可以完全不同。我们可以使用单独的实体来表示每种类型的扩展信息,然后使主实体对每种可能的扩展信息类型具有可空引用。
我可以看到两种在关系数据库中实现它的方法:
1。对主记录中子实体的可空引用
对于每种类型的子实体,主记录 table 都有一个字段引用子(扩展名)table 记录的 ID 作为外键。
这似乎是更直接的选择:要检索相关信息,我们只需按照直接引用即可。在 SQL 查询中,我们将通过外键左连接子(扩展)tables。外键的空值将为所有子 table 字段提供空值。
2。子实体的记录引用主实体的记录
我们不在主实体中存储任何引用 table。相反,子实体 table(s) 的每个记录都通过 ID 作为外键引用来自主实体 table 的记录。在 SQL 查询中,我们仍然将子 tables 左连接到主查询,并且我们为所有没有对应子记录的子 tables 字段获取空值。
???
哪种方法是正确的?第二个似乎更 relational,我们不必在 master table 中创建额外的字段,但从技术上讲,查找相关记录可能需要更多工作,因为我们必须在子 table 中搜索主 ID,而不是直接引用。或者数据库引擎是否优化这种连接以使其快速,例如使用索引?索引搜索比扫描快,但仍然比直接引用慢。加上索引占用 space。我对数据库引擎如何工作缺乏了解......或者我可能只是错过了一些明显的东西。帮助将不胜感激。
更新
得到下面的答案后,也经过一番思考,决定使用第二种方法。除了在接受的答案中所说的(更紧凑,从关系的角度来看更正确,不必处理 NULL),如果我需要删除主记录和所有相应的,它也给了我使用级联删除的很好的可能性子记录。
在这种情况下,我认为“最佳”解决方案取决于数据库的工作负载,因为这两种解决方案各有优缺点。
第二个解决方案没有空值,这通常会使查询复杂化(并且空值也使解决方案对于关系模型的纯粹主义者来说是不“正确的”),但还有其他好处:它需要更少 space(对于某些类型的操作,关系更紧凑并且需要更少的时间进行操作)。另一方面,它需要连接来访问详细数据(因此它需要为这些操作提供额外的索引)。
第一个解决方案在概念上更简单,在访问详细数据的情况下不需要联接,但需要更多 space,这会减慢某些类型的操作。
两种解决方案都在实际环境中使用。
我认为只有知道必须部署在此类数据库上的应用程序的典型工作负载是什么才能提供解决此难题的方法:更频繁(或必须具有更短的延迟)的某些查询相对于其他的?例如,仅查看一般数据的查询比需要详细数据的查询更常用?
最后,如果很难或不可能进行这种“理论”分析,唯一的另一种方法是尝试一种解决方案,但如果性能不令人满意,请准备好尝试另一种解决方案。这可以通过使用视图来完成,例如以这种方式:
从第二个解决方案开始,定义一个执行连接的视图。
在适当的时候使用基本 table 或连接视图来编写应用程序。
如果性能不令人满意,切换到另一个解决方案,通过 join 创建一个新的 table,与旧视图同名,并定义一个执行的新视图只是对非空属性的投影。
在这两种情况下,通过交换视图和基的相同名称 table,应用程序只需要进行最少的修改,您可以尝试这两种方法。
想象一下,在我们的数据模型中,我们有一个实体(数据结构),它有可选的部分。我们可以将这些 "parts" 实现为对其他(子)实体的可为空的引用。换句话说,主实体的每个实例可能有或没有其他(子)实体的单个实例与之关联,而子实体的任何实例只有一个主实体实例与之关联。所以我们有 1 到 0..1 的关系。
例如,审计日志记录有公共字段(时间戳、用户、操作)和操作特定部分(扩展信息),对于不同的操作可以完全不同。我们可以使用单独的实体来表示每种类型的扩展信息,然后使主实体对每种可能的扩展信息类型具有可空引用。
我可以看到两种在关系数据库中实现它的方法:
1。对主记录中子实体的可空引用
对于每种类型的子实体,主记录 table 都有一个字段引用子(扩展名)table 记录的 ID 作为外键。
这似乎是更直接的选择:要检索相关信息,我们只需按照直接引用即可。在 SQL 查询中,我们将通过外键左连接子(扩展)tables。外键的空值将为所有子 table 字段提供空值。
2。子实体的记录引用主实体的记录
我们不在主实体中存储任何引用 table。相反,子实体 table(s) 的每个记录都通过 ID 作为外键引用来自主实体 table 的记录。在 SQL 查询中,我们仍然将子 tables 左连接到主查询,并且我们为所有没有对应子记录的子 tables 字段获取空值。
???
哪种方法是正确的?第二个似乎更 relational,我们不必在 master table 中创建额外的字段,但从技术上讲,查找相关记录可能需要更多工作,因为我们必须在子 table 中搜索主 ID,而不是直接引用。或者数据库引擎是否优化这种连接以使其快速,例如使用索引?索引搜索比扫描快,但仍然比直接引用慢。加上索引占用 space。我对数据库引擎如何工作缺乏了解......或者我可能只是错过了一些明显的东西。帮助将不胜感激。
更新
得到下面的答案后,也经过一番思考,决定使用第二种方法。除了在接受的答案中所说的(更紧凑,从关系的角度来看更正确,不必处理 NULL),如果我需要删除主记录和所有相应的,它也给了我使用级联删除的很好的可能性子记录。
在这种情况下,我认为“最佳”解决方案取决于数据库的工作负载,因为这两种解决方案各有优缺点。
第二个解决方案没有空值,这通常会使查询复杂化(并且空值也使解决方案对于关系模型的纯粹主义者来说是不“正确的”),但还有其他好处:它需要更少 space(对于某些类型的操作,关系更紧凑并且需要更少的时间进行操作)。另一方面,它需要连接来访问详细数据(因此它需要为这些操作提供额外的索引)。
第一个解决方案在概念上更简单,在访问详细数据的情况下不需要联接,但需要更多 space,这会减慢某些类型的操作。
两种解决方案都在实际环境中使用。
我认为只有知道必须部署在此类数据库上的应用程序的典型工作负载是什么才能提供解决此难题的方法:更频繁(或必须具有更短的延迟)的某些查询相对于其他的?例如,仅查看一般数据的查询比需要详细数据的查询更常用?
最后,如果很难或不可能进行这种“理论”分析,唯一的另一种方法是尝试一种解决方案,但如果性能不令人满意,请准备好尝试另一种解决方案。这可以通过使用视图来完成,例如以这种方式:
从第二个解决方案开始,定义一个执行连接的视图。
在适当的时候使用基本 table 或连接视图来编写应用程序。
如果性能不令人满意,切换到另一个解决方案,通过 join 创建一个新的 table,与旧视图同名,并定义一个执行的新视图只是对非空属性的投影。
在这两种情况下,通过交换视图和基的相同名称 table,应用程序只需要进行最少的修改,您可以尝试这两种方法。