建模客户 <--> 地址的最佳方法

Posted

技术标签:

【中文标题】建模客户 <--> 地址的最佳方法【英文标题】:Best way to model Customer <--> Address 【发布时间】:2010-10-13 12:06:09 【问题描述】:

每个Customer 都有一个实际地址和一个可选的邮寄地址。您首选的建模方式是什么?

选项 1。Customer 具有到 Address 的外键

客户(id、phys_address_id、mail_address_id) 地址(id、街道、城市等)

选项 2。CustomerAddress 具有一对多关系,其中包含一个字段 描述地址类型

客户ID) 地址(id、customer_id、address_type、街道、城市等)

选项3.地址信息去规范化并存储在Customer

客户(id、phys_street、phys_city 等 mail_street、mail_city 等)

我最重要的目标之一是简化对象-关系映射,因此我倾向于第一种方法。你有什么想法?

【问题讨论】:

【参考方案1】:

出于正常化的所有常见原因,我倾向于第一种方法。这种方法还可以更轻松地对邮件详细信息执行数据清理。

如果您可能要允许多个地址(邮件、住宅等)或希望能够使用生效日期,请考虑这种方法

客户(id,phys_address_id) Cust_address_type(cust_id、mail_address_id、address_type、start_date、end_date) 地址(id、街道、城市等)

【讨论】:

你为什么要分开 cust_address_type 和 address 呢?两者中包含的信息可以毫无问题地放在一张桌子上。我认为 2 张桌子没有好处。 因为每个客户可以拥有 1 个或多个地址,例如计费,交付等。您还可以通过日期字段轻松跟踪历史记录。您可以在同一个地址拥有多个客户,并将每个客户视为一个实体,但为一个客户更改地址不会影响其他客户。 此外,此模型非常适合您拥有大多数地址列表并分配它们的情况(例如,如果您从邮局或类似处购买了地址列表)。或者您的数据库足够大,可以拥有您所在地区的大多数地址。 无关紧要。只需要两个表。在地址中放置一个“类型”字段,您就可以解决问题。这样做仍然允许您在上面提到的所有其他内容。无论如何,您显然可以在您的 cust_address_type 表中有多个条目,因此将实际地址组合并放入其中很简单。我可以“可能”在一个地址的多个客户中看到一些价值,但这是一个罕见的例外。为 95% 而不是 5% 的人设计。 欢迎您提出意见。在过去的几十年里,我在使用各种系统时积累了经验。【参考方案2】:

您可能需要考虑的一个重要事实(取决于您的问题领域)是人们更改地址,并且可能希望在地址更改之前通知您;这对于公用事业公司、电信公司等来说当然是正确的。

在这种情况下,您需要有一种方法为客户存储多个具有有效期的地址,以便可以提前设置地址并在正确的位置自动切换。如果这是一个要求,那么 (2) 的变体是对其建模的唯一合理方法,例如

Customer (id, ...)
Address (id, customer_id, address_type, valid_from, valid_to)

另一方面,如果您不需要满足这一点(并且您确定将来不会),那么 (1) 可能更易于管理,因为它更容易维护数据完整性,因为确保只存在一个相同类型的地址没有问题,并且连接变得更简单,因为它们只在一个字段上。

因此,根据您是否需要搬家,(1)或(2)都可以,但我会避开(3),因为您随后会重复表中地址的定义,如果您更改地址的外观,则必须添加多个列。它的性能可能略高一些,但老实说,当您在关系数据库中处理正确索引的联接时,并没有太多收获,而且在某些情况下可能会变慢 需要地址,因为客户的记录大小会更大。

【讨论】:

此地址表仅与客户关联,与订单地址或办公地址无关。【参考方案3】:

我们正在推进这样的模型:

Person (id, given_name, family_name, title, suffix, birth_date)
Address (id, culture_id, line1, line2, city, state, zipCode, province, postalCode)
AddressType (id, descriptiveName)
PersonAddress (person_id, address_id, addressType_id, activeDates)

大多数人可能认为这太过分了。然而,在我们开发的应用程序中,一个不可否认的共同主题是它们将拥有一些基本实体——人员、组织、地址、电话号码等——并且它们都希望以不同的方式组合它们。因此,我们正在预先构建一些泛化,我们 100% 确定我们有用例。

Address 表将遵循一个 table-per-hierarchy 继承方案,以根据文化区分地址;因此美国地址将包含州和邮政编码字段,但加拿大地址将包含省和​​邮政编码。

我们使用一个单独的连接表来“给”一个人一个地址。当我们的经验是这往往会使事情变得复杂时,这使我们的其他实体 - 个人和地址 - 与其他实体无关。它还使得将 Address 实体连接到许多其他类型的实体(人员、组织等)以及与链接关联的不同上下文信息(如我的示例中的 activeDates)变得更加简单。

【讨论】:

+1 这就是我会发布的设计。我用那个连接表来驱动很多功能。数据清理甚至营销邮件。当那些想要报告的人可以看到在上一个活动中邮寄给哪些客户时,它就派上用场了,等等。 到目前为止这是最好的解决方案。【参考方案4】:

第二种选择可能是我要走的路。并且如果它允许用户添加额外的地址(如果你想让他们这样做),他们可以随意切换以进行运输等。

【讨论】:

【参考方案5】:

我更喜欢#1。良好的规范化并清楚地传达意图。此模型还允许 same 地址对象(行)用于两个地址,我发现这是非常有价值的。过多地重复这些信息很容易迷失方向。

【讨论】:

我很想知道这是如何在 UI 中完成的。我已经建立了一个允许共享地址对象的系统,但用户没有得到这个概念。 根据您的描述,我认为您的意思是第二个。它们都被标记为 #1【参考方案6】:

在回答这类问题时,我喜欢使用DDD 的分类。如果它是一个实体,它应该有一个单独的 ID,如果它是一个值对象,它不应该。

【讨论】:

【参考方案7】:

选项 3 限制性太强,选项 1 不能扩展以允许其他地址类型而不更改架构。 选项 2 显然是最灵活的,因此也是最佳选择。

【讨论】:

我同意它是最灵活的,但值得在 ORM 复杂性上进行权衡吗?【参考方案8】:

在我现在编写的大多数代码中,每个客户都有一个且只有一个物理位置。这是我们的业务合作伙伴的法人实体。因此,我将街道、城市等放在客户对象/表中。通常这是可行的最简单的方法。

当需要额外的邮寄地址时,我将其放在单独的对象/表中,以免客户对象过于混乱。

在我职业生涯的早期,我疯狂地规范了一个订单,该订单引用了一个引用了送货地址的客户。这使事情变得“干净”,但使用起来又慢又不雅。现在我使用一个只包含所有地址信息的订单对象。实际上,我认为这更自然,因为客户可能会更改他的(默认?)地址,但 2007 年发送的货件地址应该始终保持不变 - 即使客户在 2008 年搬家。

我们目前实施VerySimpleAddressProtocol in out 项目以标准化使用的字段。

【讨论】:

您的地址数据更改无效。如果在不得更改的实体中引用单个地址行,则不得更改。如果地址要更改,则必须创建新记录,如果等于现有记录,则使用现有记录。你保存历史。我们在一家保理公司和电子商务产品中使用了这种方法。您可以使用与语言相关的规则来清理用户输入地址数据,以达到非常好的比较质量。或使用邮政公司等外部提供商。【参考方案9】:

我会选择第一个选项。在这些情况下,我对 YAGNI 感到非常厌倦(你不会需要它)。我数不清有多少次我查看了具有一对多表的模式,这些表“只是以防万一”已经很多年了。如果只需要两个,就使用第一个选项;如果将来需求发生变化,那么就改变它。

【讨论】:

【参考方案10】:

在很多情况下:视情况而定。

如果您的客户处理多个地址,那么一对多关系将是合适的。您可以在地址上引入一个标志,表明地址是否用于发货或账单等。或者您将不同的地址类型存储在不同的表中,并在客户上具有多对一的关系。

在您只需要知道客户的一个地址的情况下,为什么要对多对多进行建模?一对一的关系可以满足您的需求。

重要提示:仅在遇到性能问题时才进行非规范化。

【讨论】:

【参考方案11】:

我会选择选项 1。如果您愿意,您甚至可以稍微修改一下以保留地址历史记录:

Customer   (id, phys_address_id, mail_address_id)
Address    (id, customer_id, start_dt, end_dt, street, city, etc.)

如果地址发生变化,只需结束当前地址的日期并在Address 表中添加一条新记录。 phys_address_idmail_address_id 始终指向当前地址。

这样您可以保留地址历史记录,您可以在数据库中存储多个邮寄地址(默认为mail_address_id),如果实际地址和邮寄地址相同,您只需指向@987654326 @ 和 mail_address_id 在同一条记录中。

【讨论】:

-1 - 通过这种设计,您在 Address 和 Customer 表之间获得了双向依赖关系,这使得数据库架构更加脆弱,同时没有增加任何实际好处。【参考方案12】:

好线程。我花了一段时间考虑最合适的模式,我得出的结论是 quentin-starin 的解决方案是最好的,除了我在他的 PersonAddress 中添加了 start_dateend_date 字段桌子。我还决定添加 notesactivedeleted

deleted 用于软删除功能,因为我认为我不想仅仅通过从联结表中删除记录来丢失以前地址的踪迹。我认为这是非常明智的,其他人可能想要考虑的事情。如果不这样做,则可能需要修改纸质或电子文档以尝试追踪地址信息(最好避免这样做)。

注释我认为这是一种要求,但这可能只是偏好。我花时间在回填练习中验证数据库中的地址,有些地址可能非常模糊(例如农村地址),我认为至少允许在记录地址中保留有关该地址的注释非常有用。

我想听听意见的一件事是 address 表的唯一索引(同样,指的是 quentin-starin 示例中的同名表。你认为它应该是应该强制执行唯一索引(作为可能跨越所有非空/必填字段的复合索引)?这似乎是明智的,但可能仍然难以阻止重复数据,因为邮政/邮政编码并不总是对单个属性唯一。即使国家、省和城市字段是从参考数据(它们在我的模型中)填充的,地址行中的拼写差异也可能不匹配。最好避免这种情况的唯一方法可能是运行一个或多个DB查询来自传入的表单字段以查看是否找到了可能的重复项。另一个安全措施是让用户可以选择从已经链接到该人的数据库中的地址中选择并使用它来自动填充。我认为这个可能是你只能自己的情况明智并采取预防措施停止重复,但接受它迟早会(并且可能会)发生。

对我来说另一个非常重要的方面是将来编辑 address 表记录。假设您有 2 个人都列在:-

11 随便街 无论哪个城市 Z1P C0D3

允许将相同的地址表记录分配给不同的实体(个人、公司)是否应该被视为危险?然后假设用户意识到其中一个人住在 111 What Street 并且有一个错字。如果您更改该地址,它将同时更改这两个实体。我想避免这种情况。我的建议是让 MVC 中的模型(在我的例子中是 php Yii2)在创建已知与该客户相关的新地址时查找现有的 address 记录(SELECT * FROM address INNER JOIN personaddress ON personaddress.address_id = address.id WHERE personaddress.person_id = current person being editing ID)并为用户提供使用该记录的选项(正如上面所建议的那样)。

我觉得将同一个地址链接到多个不同的实体只是自找麻烦,因为这可能是拒绝以后编辑 地址 记录(不切实际)或冒着将来编辑记录可能会损坏与正在编辑的地址记录之外的其他实体相关的数据。

我很想听听人们的想法。

【讨论】:

以上是关于建模客户 <--> 地址的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

ASP .NET MVC 5 - 客户-地址一对一关系

在 DynamoDB 中建模计数排行榜的最佳设计模式

关闭异步 resteasy 客户端调用的最佳方法

用核心数据(Mac OS X 可可)对 _ordered_ 项目列表建模的最佳方法是啥?

在 Spark 中将可选参数建模为 UDF 的最佳方法是啥?

建模音乐(音符)以在特定时间快速搜索音符的最佳方法