8

我们在应用程序中有一个要求,我们需要存储引用以供以后访问。

示例:用户可以一次提交发票,并且该发票包含的所有参考资料(客户地址、计算的金额、产品描述)和计算应该随着时间的推移而存储。

我们需要以某种方式保存引用,但是如果产品名称发生变化怎么办?因此,我们需要以某种方式复制所有内容,以便稍后记录,并且不受将来更改的影响。即使产品被删除,它们也需要稍后在存储发票时进行审查。

这里关于数据库设计的最佳实践是什么?甚至什么是最灵活的方法,例如当用户想要稍后编辑他的发票并从数据库中恢复它时?

谢谢!

4

5 回答 5

11

这是一种方法:

在此处输入图像描述

本质上,我们从不修改或删除现有数据。我们通过创建一个新版本来“修改”它。我们通过设置 DELETED 标志来“删除”它。

例如:

  • 如果产品更改价格,我们在 PRODUCT_VERSION 中插入一个新行,同时旧订单保持连接到旧 PRODUCT_VERSION 和旧价格。
  • 当买家更改地址时,我们只需在 CUSTOMER_VERSION 中插入一个新行并将新订单链接到该行,同时保持旧订单链接到旧版本。
  • 如果产品被删除,我们并没有真正删除它——我们只是设置了 PRODUCT.DELETED 标志,所以历史上为该产品所做的所有订单都保留在数据库中。
  • 如果客户被删除(例如因为他要求注销),设置 CUSTOMER.DELETED 标志。

注意事项:

  • 如果产品名称需要唯一,则无法在上述模型中以声明方式强制执行。您要么需要将 NAME 从 PRODUCT_VERSION“提升”为 PRODUCT,将其设为密钥并放弃“演变”产品名称的能力,要么仅在最新的 PRODUCT_VER 上强制唯一性(可能通过触发器)。
  • 客户的隐私存在潜在问题。如果客户从系统中删除,可能需要从数据库中物理删除其数据,而仅设置 CUSTOMER.DELETED 不会这样做。如果这是一个问题,要么清空所有客户版本中的隐私敏感数据,要么将现有订单与真实客户断开连接并将它们重新连接到特殊的“匿名”客户,然后物理删除所有客户版本。

该模型使用了大量的识别关系。这会导致“胖”外键,并且可能是一个存储问题,因为 MySQL 不支持前沿索引压缩(与 Oracle 不同),但另一方面InnoDB总是将数据聚集在 PK 上,这集群可以有利于性能。此外,JOIN 也不太需要。

具有非识别关系和代理键的等效模型如下所示:

在此处输入图像描述

于 2012-06-11T21:58:52.027 回答
1

您可以在产品表中添加一列,指示它是否正在销售。然后,当产品被“删除”时,您只需设置标志,使其不再作为新产品可用,但您保留数据以供将来查找。

要处理名称更改,您应该使用 ID 来引用产品,而不是直接使用名称。

于 2012-06-11T19:55:58.943 回答
1

我相信您知道,您面临的问题是数据库规范化的结果。解决此问题的方法之一可以从商业智能技术中获取 - 将数据以非规范化状态归档在数据仓库中。

标准化数据:

  • 订单表
    • 订单编号
    • 客户ID
  • 客户表
    • 客户ID
    • ETC
  • 项目表
    • 项目 ID
    • 项目名称
    • 项目价格
  • 订单明细表
    • ItemDetailId
    • 订单编号
    • 项目 ID
    • 项目数量
    • ETC

当查询和存储去规范化时,数据仓库表看起来像

  • 订单编号
  • 客户ID
  • 顾客姓名
  • 客户地址
  • (其他客户字段)
  • ItemDetailId
  • 项目 ID
  • 项目名称
  • 项目价格
  • (其他 OrderDetail 和 Item 字段)

通常,有某种计划的作业可以按计划将数据从规范化数据中提取到数据仓库中,或者如果您的设计允许,可以在订单达到特定状态时完成。(例如已发货)可能会在每次状态更改时存储记录(使用名为 OrderStatus 的字段跟踪当前状态),因此完全非规范化的数据可用于操作/履行过程的每个步骤。将数据归档到仓库的时间和方式将根据您的需要而有所不同。


以上涉及很多开销,但我知道的另一种常见方法会带来更多开销。

另一种方法是将表设为只读。如果客户想要更改他们的地址,您无需编辑他们现有的地址,而是插入一条新记录。

因此,如果我在 Jamnuary 第一次在您的网站上订购时的地址是 AddressId 12,那么我会在 7 月 4 日搬家,我会收到一个与我的帐户绑定的新 AddressId。(说 AddressId 123123,因为您的网站非常成功并吸引了大量客户。)

我在 7 月 4 日之前下的订单会关联到 AddressId 12,而在 7 月 4 日或之后下的订单会有 AddressId 123123。

对需要保留历史数据的每个表重复该模式。


我确实有第三种方法,但搜索它很困难。我只在一个应用程序中使用它,它实际上在这个单一实例中运行良好,它有一些非常具体的业务需求,可以完全按照特定时间点的方式重建数据。除非我有类似的业务需求,否则我不会使用它。

在特定状态下,将数据序列化为 Xml 文档或可用于重建数据的其他文档。这允许您将数据保存为序列化时的数据,保留原始表结构和相关性。

于 2012-06-11T19:59:50.477 回答
1

你开启了纯粹主义和实用主义方法之间的永恒争论。

从数据库规范化的角度来看,您“应该”保留所有相关数据。换句话说,假设产品名称更改,保存更改日期,以便您可以及时返回并使用该产品名称以及当天存在的所有其他数据重建您的发票。

“去”规范化的方法是将发票视为“时刻”,在相关表格中记录当天的实际数据。这种方法可以让您在没有任何依赖关系的情况下提取该发票,但您永远无法从头开始重新创建该发票。

于 2012-06-11T20:01:47.343 回答
0

当您拥有对时间敏感的数据时,您可以使用 product 和 Customer 表作为查找表,并将信息直接存储在 Orders/orderdetails 表中。

因此订单表可能包含客户姓名和地址,详细信息将包含有关产品的所有相关信息,尤其是价格(您永远不想依赖产品表来获取订单时的初始查找之外的价格信息)。

这不是非规范化,数据会随着时间而变化,但您需要历史值,因此您必须在创建记录时存储它,否则您将失去数据完整性。您不希望您的财务报告突然显示您去年的销售额增加了 30%,因为您有价格更新。那不是你卖的。

于 2012-06-11T20:50:48.377 回答