我遇到了一个相当有趣的情况,我需要指导来帮助我设计遵循“最佳实践”或完成“推荐方式”的数据库模式。
我的困境如下:
我有一个Event
表,其中包含Id,Name,Date等基本属性。它需要一个地址信息,因此最直接的方法是使用街道,城市,国家等字段扩展表格我还有一个User表,它还需要存储地址数据。所以正确的做法是创建第三个名为Address的表,并在Address / User和Address / Event之间建立关系。这是棘手的部分。哪个表应该包含主键/外键。
一种方法是使用Address
和EventId
等列扩展表UserId
。因此表Event
和User
将是“父”表,地址将是“子”表。 Address
表将保存用户/事件的Id主键的外键。
|EventTable:| |UserTable: | |AddressTable|
| | | | | |
|EventId PK | |UserId PK | |AddresId PK |
|Name | |Name | |Street |
|OtherColumn| |OtherColumn| |City |
|EventId FK |
|UserId FK |
我从这种设计中看到的两个缺点是,每行AddressTable
都会包含额外的不必要的Null字段。例如,如果address指定用户地址,则列EventId
将为Null,如果地址行指定事件地址,则列UserId
将为Null。
第二个缺点是,无论何时我添加一个也需要连接到地址表的新表,我都需要在表地址中添加另一列来引用新表的主键。
第二种可能性是使用Event
的主键列扩展表User
和Address
,以便它们成为关系中的外键。
|EventTable:| |UserTable: | |AddressTable|
| | | | | |
|EventId PK | |UserId PK | |AddresId PK |
|Name | |Name | |Street |
|OtherColumn| |OtherColumn| |City |
|AddressId FK| |AddressId FK|
除了我在外键上启用级联删除时我现在有疑问,这个解决方案一切都会很完美。对我来说,自然的思维方式是当我删除数据库的事件或用户时,我也希望删除它们的地址。但在这种设计中,地址表是父级,而用户/事件是子级。因此,当我删除启用了级联删除的地址条目时,我也会删除事件/用户条目。从逻辑上讲,这对我来说并没有多大意义。它应该是另一种方式,这是我无法解决的问题。也许第二种设计是可以接受的,我只是无缘无故地迷惑自己。
理想情况下,我喜欢想出这样的设计,通过启用级联删除,我先删除事件或用户,然后自动删除它们的地址。
我知道联合表有第三种选择,但这只适用于多对多关系,以及用户/事件应该只包含一个地址的内容。
谢谢!
答案 0 :(得分:1)
地址确实很棘手。
首先,地址是一个独立的东西 - 它的存在是你无法控制的,只要它的地方议会想要它就存在。另一个重要的事情 - 地址往往会一次又一次地重复使用,特别是如果我们谈论的是大型活动或短期租赁住宿。
考虑到所有这些,很明显选项1只是完全错误,与现实无关。第二个更好,但仍然错过了很多,但在这种情况下,更多地取决于你愿意走多远。
例如,如果要存储任何类型实体的地址更改历史记录,则需要历史记录表 - 再次,有几种可能的设计。您可以使用以下字段创建单个地址历史记录表:
AddressId (PK)
TenantId (PK)
StartDate (PK)
EndDate
,其中TenantId
将引用一个超类型表,该表将成为可以使用地址的所有实体的父类。这样的表(不是超类型表)也有助于防止(或允许?)在任何给定时间由超过1个租户同时使用相同的地址。
这只是冰山一角:)
答案 1 :(得分:0)
只要您对两个FK保持唯一约束,联合表仍然是一个选项。但是,第二种选择可能是最好的。为了让删除按照你想要的方式运行,我建议在从EventTable和UserTable中删除时设置一个触发器。
答案 2 :(得分:0)
第二种方法对我来说似乎最干净。毕竟,您可以让多个用户拥有相同的地址。但是,我应该注意,并不清楚"事件"地址和" person"地址是一回事。例如,一个人"地址可能有一个邮政编码和一个"事件"地址可能描述到达某个地点的不同方式。
在任何情况下,你都会向后级联。例如,当您删除用户时,您正在考虑倒退。什么都不会发生。问题是当您从地址中删除某些内容时会发生什么。然后将删除相应的用户和事件。级联的目的是在主键发生变化时保持关系完整性。
如果要在删除用户/事件时删除地址,那么我会建议触发器。但是,这对于关系完整性来说不是必需的。
答案 3 :(得分:0)
因为你给出选项1的原因是禁止的。
使用选项2,您不必担心未使用的地址记录。实际上,它们可能会在创建新事件或用户期间变得有用,因为您可以在地址"数据库"中提供搜索工具。更进一步,您甚至可以决定使用从某个地址提供程序下载的数据预填充地址表。然后搜索工具将变得非常有用。
一旦您计划拥有一个大地址列表,您可能希望将一个地址分解为它自己的层次结构:街道属于某个城市,城市属于某个国家/地区。当然,在实践中,一条街可以由几个城市共享,你可以决定在那里建立一个n对n的关系,或者你可以选择n-to-1,你有一些(但实际上非常很少)街道重复。
正如您所看到的,这可以用得很远,并且会在编写代码时花费更多精力来管理它。
另一方面,如果您对保留未使用的地址不感兴趣,可以通过事件和用户表上的删除触发器进行管理,这将检查相关地址是否已成为孤立状态,如果是,则删除它。但是,在同一时间发生这种情况并不重要,因为执行删除操作可能需要更长时间才能执行甚至失败,从而影响用户体验。最好以异步方式执行此操作,让预定的作业每周进行一次清理。