为 table 设计 sql table 关系的最佳解决方案是什么,它将被其他 table 所使用

What is the best solution to design sql table relationship for a table that will be used by other tables

我遇到了一个相当有趣的情况,我需要指导来帮助我设计遵循 "best practises" 或完成 "the recommended way" 的数据库架构。

我的困境如下:

我有一个 Event table 具有基本属性,例如 Id、名称、日期等。它需要一个地址信息,所以最直接的方法是扩展 table 包含街道、城市、国家/地区等字段。好吧,我还有一个用户 table 也需要存储地址数据。因此,正确的做法是创建第三个 table,称为 Address,并在 Address/User 和 Address/Event 之间建立关系。这是棘手的部分。哪个 table 应该持有主 key/foreign 键。

  1. 一种方法是使用 EventIdUserId 等列扩展 table Address。所以 tables EventUser 将是 "parent" table,地址将是 "child" table。 Address table 会将外键保存到 User/Event 的 ID 主键。

    |EventTable:|  |UserTable: | |AddressTable|
    |           |  |           | |            |
    |EventId PK |  |UserId PK  | |AddresId PK |
    |Name       |  |Name       | |Street      |
    |OtherColumn|  |OtherColumn| |City        |
                                 |EventId FK  |
                                 |UserId FK   |
    

    我从这种设计中看到的两个缺点是每一行 AddressTable 都会包含额外的不必要的 Null 字段。例如,如果地址指定用户地址,则 EventId 列将为空,如果地址行指定事件地址,则列 UserId 将为空。

    第二个缺点是任何时候我添加一个新的 table 也需要连接到地址 table 然后我需要添加另一列到 table 地址引用新 table 的主键。

  2. 第二种可能性是用Address的主键列扩展tables EventUser,这样它们就是外键在关系中。

    |EventTable:|  |UserTable: | |AddressTable|
    |           |  |           | |            |
    |EventId PK |  |UserId PK  | |AddresId PK |
    |Name       |  |Name       | |Street      |
    |OtherColumn|  |OtherColumn| |City        |
    |AddressId FK| |AddressId FK|                     
    

    使用此解决方案一切都会很完美,除了我现在对外键启用级联删除有疑问。对我来说,自然的思维方式是,当我删除数据库的事件或用户时,我希望也删除它们的地址。但在这样的设计中,地址 table 是父地址, User/Event 是子地址。因此,当我删除启用了级联删除的地址条目时,我也会删除 Event/User 条目。从逻辑上讲,这对我来说没有太大意义。应该反过来,这就是我无法解决的问题。也许第二种设计是acceptable,我无缘无故地把自己弄糊涂了。

理想情况下,我很乐意提出这样的设计,通过启用级联删除,我首先删除事件或用户,然后他们的地址将被自动删除。

我知道联合 table 有第三种选择,但这仅适用于多对多关系,如果 User/Event 应该只包含一个地址怎么办。

谢谢!

联合表仍然是一种选择,只要您对两个 FK 保持唯一约束。但是,第二种选择可能是总体上最好的。为了使删除按您的预期方式运行,我建议设置一个触发器以从 EventTable 和 UserTable 中删除。

第二种方法对我来说似乎是最干净的。毕竟,例如,您可能有多个用户使用相同的地址。但是,我应该指出,目前尚不清楚 "event" 地址和 "person" 地址是一回事。例如,"person" 地址可能有邮政编码,"event" 地址可能描述到达某个位置的不同方式。

无论如何,你都有向后级联。例如,当您删除一个用户时,您是在倒退思考。没有什么可以解决的。问题是当您从地址中删除某些内容时会发生什么。然后相应的用户和事件将被删除。级联的目的是在主键更改时保持关系完整性。

如果你想在users/events被删除时删除地址,那么我建议使用触发器。但是,这对于关系完整性不是必需的。

地址确实很棘手。

首先,地址是一个独立的东西 - 它的存在是你无法控制的,而是只要当地议会想要它就存在。另一件重要的事情 - 地址往往会被一次又一次地重复使用,尤其是当我们谈论大型活动或短期出租住宿时。

考虑到所有这些,很明显选项 1 完全错误并且与现实不相关。第二种更好,但仍然遗漏了很多,不过在这种情况下,它更多地取决于你愿意走多远。

例如,如果您想存储任何类型实体的地址更改历史记录,您将需要历史记录 table(s) - 同样,有几种可能的设计。您可以使用以下字段创建单个地址历史记录 table:

AddressId (PK)
TenantId (PK)
StartDate (PK)
EndDate

,其中 TenantId 将引用超类型 table,它将成为所有可以使用地址的实体的父类。这样的 table(不是超类型)也有助于防止(或允许?)在任何给定时间超过 1 个租户同时使用同一地址。

而这只是冰山一角:)

由于您给出选项 1 的原因不可行。

使用选项 2,您不必担心未使用的地址记录。事实上,它们可能会在创建新事件或用户时变得有用,因为您可以在您的地址 "database" 中提供搜索工具。更进一步,您甚至可以决定使用从某些地址提供商下载的数据预填充地址 table。那么搜索工具就会变得非常有用。

一旦您计划拥有一个大地址列表,您可能希望将一个地址分解成它自己的层次结构:一条街道属于一个城市,一个城市属于一个国家。当然,在实践中,一条街道可以被多个城市共享,你可以决定在那里建立一个 n 对 n 的关系,或者你可以选择 n 对 1,在那里你有一些(但实际上非常很少)街道重复。

正如您所看到的,这可以走得很远,并且会导致围绕它编写代码来管理它的更多工作。

另一方面,如果您对保留未使用的地址不感兴趣,您可以通过事件和用户 tables 上的删除触发器来管理它,这将检查相关地址是否已成为孤立地址,以及是否所以,删除它。但是,这在同一时间发生并不重要,因为您的删除操作可能需要更长时间才能执行甚至失败,从而影响用户体验。最好异步执行此操作,让计划的作业每周左右进行一次清理。