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

22

每个客户(Customer)都有一个物理地址和一个可选的邮寄地址。你希望如何建模这个问题?

选项1. 客户(Customer)有到地址(Address)的外键

   客户(Customer)  (id, phys_address_id, mail_address_id)
   地址(Address)    (id, street, city, 等等)

选项2. 客户(Customer)地址(Address)之间有一对多关系,其中包含一个字段来描述地址类型

   客户(Customer)  (id)
   地址(Address)    (id, customer_id, address_type, street, city, 等等)

选项3. 地址信息被去规范化并存储在客户(Customer)

   客户(Customer)  (id, phys_street, phys_city, 等等, mail_street, mail_city, 等等)

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

12个回答

11

出于规范化的常见原因,我更倾向于采用第一种方法。此外,这种方法还可以更轻松地对邮寄详细信息进行数据清理。

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

   客户 (id, phys_address_id)
   客户地址类型 (cust_id, mail_address_id, address_type, start_date, end_date)
   地址 (id, street, city, etc.)

你为什么要将cust_address_type和address分开呢?两者所包含的信息可以毫无问题地放在一个表中。我认为使用三个表并没有比使用两个表更有益处。 - JM4
1
此模型也非常适用于您拥有大多数地址列表并分配它们的情况(例如,如果您从邮局或类似机构购买了地址列表)。或者您的数据库足够大,可以包含您所在地区的大多数地址。 - Karl
2
需要两个表格。在地址中加入“类型”字段即可解决问题。这样做仍然允许您执行上述所有其他操作。您显然可以在cust_address_type表中拥有多个条目,因此将实际地址组合并放置其中是简单的。我可以“可能”看到一个地址有多个客户的价值,但这是一个罕见的例外。为95%的情况设计,而不是为5%的情况设计。 - JM4
这个设计的问题在于它假设只有一个实体类型(在这种情况下是客户)需要地址。如果你还有公司实体、供应商实体等,它们也都可以有地址,你会创建supplier_address、company_address等吗?为什么physical_address_id仍然是Customer表的一部分,而mail_address却在交叉引用表中? - DhafirNz
我是在特别回答原问题。我曾经使用过的一个系统,在每个模块中都采用了这种方法来跟踪地方政府ERP的地址。它在费率模块中跟踪缴费人地址,在物业模块中跟踪物业地址等等。在名称和地址模块中,它有名称和地址的主列表。客户ID是与名称相关联的链接,地址ID允许您对实体类型拥有多个地址。如果每个实体类型都有相同的地址,则在名称和地址模块中只有一个条目。 - Karl
显示剩余2条评论

7

在考虑问题域时,您可能需要考虑一个重要的事实:人们会更改地址,并且可能希望提前告知您他们的地址更改;对于公用事业公司、电信公司等来说,这显然是正确的。

在这种情况下,您需要一种存储客户多个地址及其有效日期的方法,以便可以提前设置地址并在正确时间自动切换。如果这是一个要求,则变体(2)是建模的唯一明智方式,例如:

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

另一方面,如果您不需要考虑这一点(并且您确定未来也不会需要考虑),那么可能(1)更容易管理,因为维护数据完整性要简单得多,因为不存在确保同一类型的地址仅存在一个地址的问题,并且连接更简单,因为它们只在一个字段上。因此,无论是(1)还是(2),都可以根据是否需要房屋搬迁来选择,但我建议避免使用(3),因为您将在表中重复定义地址,并且如果更改地址的外观,则必须添加多个列。它可能稍微更具性能,但是老实说,当您在关系数据库中处理适当索引的连接时,几乎没有什么可获得的好处,而在某些情况下,需要地址的记录大小较大,它很可能会更慢。

这个地址表只与客户的地址有关,而不涉及订单地址或办公室地址。 - kta

6
我们正在采用这样的模型前进:
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%确定我们会用到这些内容。

地址表将遵循表分层继承方案,根据文化差异区分地址;因此,美国地址将具有州和邮政编码字段,但加拿大地址将具有省份和邮政编码。

我们使用单独的连接表来“给”一个人一个地址。这使得我们的其他实体-人员和地址-与其他实体没有联系,因为我们的经验是这倾向于使事情变得复杂。它还使得将地址实体连接到许多其他类型的实体(人员、组织等)以及与链接相关的不同上下文信息(例如我的示例中的活动日期)变得更加简单。


1
+1 那就是我想发布的设计。我使用那个连接表来驱动很多功能。数据清洗甚至营销邮件都可以用它。当那些想要报告的人可以看到上次活动中发送邮件给哪些客户时,它非常有用,等等。 - Taptronic
到目前为止,这是最好的解决方案。 - kta

4
第二个选项可能是我会选择的方式。而且如果你想让用户添加额外的地址(如果你想让他们这样做),他们可以随意切换以进行运输等操作。

3

我更喜欢第一种方法。良好的标准化可以清晰地传达意图。该模型还允许使用相同的地址对象(行)用于两个地址,我发现这非常有价值。过度复制此信息很容易使人迷失方向。


1
我对UI中是如何完成的很感兴趣。我已经建立了一个允许地址对象共享的系统,但用户并没有理解这个概念。 - cdonner
根据你的描述,我认为你指的是第二个。它们都标记为#1。 - singpolyma

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

2

选项3过于严格,选项1无法扩展以允许其他地址类型而不改变模式。选项2显然是最灵活的,因此是最好的选择。


我同意它是最灵活的,但在ORM复杂性方面是否值得权衡? - Tony the Pony

2
在我现在编写的大多数代码中,每个客户都有一个且仅有一个物理位置,这是我们业务伙伴的法律实体。所以我将街道、城市等信息放在客户对象/表中。通常这是工作最简单的方法,并且它也起作用。
当需要额外的邮寄地址时,我会将它放在一个单独的对象/表中,以避免使客户对象过于混乱。
早些时候,在我的职业生涯中,我进行了大量规范化,让订单引用客户,然后引用运送地址。这使得事情“干净”,但使用起来又慢又不优雅。现在,我使用一个只包含所有地址信息的订单对象。实际上,我认为这更自然,因为客户可能会更改他的(默认?)地址,但2007年发送的货件的地址应该始终保持不变-即使客户在2008年搬家。
我们目前在项目中实施VerySimpleAddressProtocol 以标准化使用的字段。

1
您的地址数据更改无效。如果在实体中引用了不应更改的地址行,则不应更改单个地址行。如果要更改地址,则必须创建新记录,如果与现有记录相同,请使用现有记录。您可以保留历史记录。我们在保理公司和电子商务产品中使用了这种方法。您可以使用语言相关规则清理用户输入的地址数据,以达到非常好的比较质量。或者使用像邮政公司这样的外部提供商。 - djmj

1

我会选择选项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),如果物理地址和邮寄地址相同,则只需将phys_address_idmail_address_id指向同一条记录即可。

使用此设计,地址表和客户表之间存在双向依赖关系,这使得数据库模式更加脆弱,同时没有真正带来任何好处。 - Greg Beech

1

很好的帖子。我花了一些时间考虑最合适的架构,得出结论,quentin-starin的解决方案是最好的,但我已经在他的PersonAddress表中添加了start_dateend_date字段。我还决定添加notesactivedeleted

deleted用于软删除功能,因为我认为不想通过从联接表中删除记录来丢失以前的地址信息。我认为这是相当明智的,其他人也可能要考虑这一点。如果不是这样做,就可能留给纸质或电子文档的修订来尝试跟踪地址信息(最好避免)。

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

我希望听取关于地址表唯一索引的意见(再次指的是quentin-starin示例中同名的表)。您认为应该强制执行唯一索引(可能跨越所有非空/必填字段的复合索引)吗?这似乎很明智,但仍然很难防止重复数据,因为邮政/邮编并不总是唯一的。即使国家、省份和城市字段是从参考数据中填充的(在我的模型中是这样),地址行中的拼写差异也可能不匹配。最好避免这种情况的唯一方法可能是从传入的表单字段运行一个或多个数据库查询,以查看是否已找到可能的重复项。另一个安全措施是给用户选择从已经链接到该人的数据库中选择地址,并使用它来自动填充。我认为这可能是一个只能明智地采取预防措施来防止重复,但接受它迟早会发生的情况。
对我来说另一个非常重要的方面是将来编辑地址表记录。假设有2个人都列在以下地址:-
11 Whatever Street Whatever City Z1P C0D3
不应该允许将相同的“地址”表记录分配给不同的实体(个人、公司),否则会被认为是危险的。假设用户意识到这些人中的一个住在111号某街,但有一个错字。如果更改该地址,则会同时更改两个实体的地址。我想避免这种情况。我的建议是,在创建与客户相关的新地址时,使MVC模型(在我的情况下是PHP Yii2)查找现有的“地址”记录(SELECT * FROM address INNER JOIN personaddress ON personaddress.address_id = address.id WHERE personaddress.person_id = {current person being edited ID}),并向用户提供使用该记录的选项(正如上面所建议的一样)。
我认为将同一地址链接到多个不同实体只会引发麻烦,因为这可能会导致拒绝以后编辑“地址”记录(不切实际)或者冒着未来编辑记录可能损坏其他实体相关数据的风险,而这些实体不属于正在编辑“地址”记录的那个实体。
我很想听听大家的想法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接