问题描述
问题
考虑以下数据库表:
- 产品
- 订购
- 订单详细信息
- 用户
产品具有以下列:
Product_Name,Product_Description,Product_Size,Product_Cost,Product_Unit
订单包含以下列:
Order_number,Order_Total,Order_Status,Order_Payment_Status,Order_UserId (Fk of user table),Order_date
“订单详细信息”具有以下列:
OrderDetails_OrderId(Fk of Order table),OrderDetails_ProductId (Fk of Product table),OrderDetails_Quantity
用户具有列:
User_Name,User_Phone (unique),User_Email (unique),User_Address
考虑,要下达,打包,交付,取消,关闭的订单状态。
现在,用户u1有3个订单:
现在的情况是用户u1更新他的信息。更新后的信息应仅在O1和O2中开始反映,因为它们仍处于放置状态;而O3已经为用户关闭,现在仅开放给管理员编辑-因此O3仍应反映以前存在的旧用户信息。使用当前的数据库结构-这是不可能的。
类似地,如果管理员以关闭的顺序编辑那里的产品,则不应以关闭的顺序显示编辑内容。
您可能已经发现,上面描述的当前结构是一个简单的与外键相关的结构,其中一个编辑显然会直接反映所有相关实体。
我想出了什么解决方案?
解决方案1:版本化 切勿更新任何行/条目。始终为任何更改添加新行(软更新)。继续添加带有一些标记/标识/时间戳/审核跟踪(已编辑)的行,并使用映射表将版本与订单表进行映射。
即
User_Name | User_Phone | User_Email | User_Address | Version/Timestamp
abc | 123 | abc@email.com |someaddres | v1
abc | 234 | abc@email.com |someaddress | v2
new mapping table
version | order_id
v1 | o3
此解决方案的缺点:
- 同一实体的同一表中有多个条目-那么我们将无法使用唯一键。在这里,电话和电子邮件是唯一的,但是如果我们采用这种方法,则必须删除唯一的索引。
- 所有具有User表外键的表(与顺序无关)都会产生影响。例如,user_Feedback表仅具有用户的外键,但是现在由于有多个条目具有同一用户的不同版本,因此该表将受到不必要的影响。
- 当用户数量增加时,某些查询的性能将会受到影响。
- 用户的电子邮件是用于登录的身份。在同一张表中无论如何都无法进行复制。
否,这不是审核跟踪! 根据我们的要求,我们要保留给o3的旧信息仍应保持可编辑状态。因此,还必须对这些编辑进行审核。因此,审核跟踪将完全是一个单独的包装器。
解决方案2:订单关闭后,创建一个新表,其中包含保存所有各个表的json / dump的列 即
new table
order_id | JsonOfUser | JsonOfProductDetails | ...
o3 | {"name":"abc",...} | ... |
此解决方案的缺点:
- 转储的内容是可编辑的,但是这里转储的数据很难编辑,因为现在表已更改,并且此表具有有效地要进行编辑的string / jsonb列,并且其他导航也已删除(非规范化),因此所有可能由于编辑而发生的计算更改也必须手动完成。
- 此表中审核的审核跟踪会很麻烦,因为我们将在此处审核json编辑。
- 深层子jsons-增加代码复杂度。
解决方案3:创建结构完整的所有表的副本,这些副本与根据状态事件的顺序相关联 即
User_Common
User_Closed
对于订单O3,在关闭时,user_common的所有详细信息将被复制到User_closed,具有User_common表的外键的订单O3将被User_Closed表的外键更改。现在,o3中的任何更改都将有效地覆盖旧数据,所有其他未结/下达的订单仍可以从User_common获取更新的信息。
此解决方案的缺点:
- 假设符合此要求的订单中恰好有10个这样的表,则必须制作每个表的副本
- 基于订单的事件/状态,每个实体现在都由两个表有效地表示-可能发生同步问题和数据保存问题-即可维护性。
- 此处订购表的外键正在更改。如此有效地在订单表中,将有两个外键列:一列用于user_common,另一列用于user_closed。因此,当订单打开时,user_closed外键将保持为空,而当订单关闭时,它将被填充。在此之前,仍然会发生1种数据操作,一种是将有关订单关闭的信息从user_common表复制到user_closed。
- 在代码中,我们总是必须根据订单状态(另一个DB调用)来使数据库检查是否应该在公用表或封闭表中进行查找-导致代码级的认知复杂性
这是对我们的要求和研究中提出的解决方案的最小伪造。在不增加不必要的复杂性的情况下,可以满足此要求的实际可行设计是什么?
解决方法
使用具有 User 表的方法,为 valid_since 列添加另一列。将“禁止删除”策略应用于用户。
如果衡量性能问题,请为用户添加一个固定/物化(在内存中)视图,该视图仅显示最新地址-使用该视图获取新下订单的user_id并加入以显示订单那是开放的。对于使用外键进入 User 的现有订单的联接,大多数情况下并不关心实际的 user_id 到底有多少(简化)。 / p>
在用户上使用 after_insert 触发器将新的 user_id 传播到表中 Order 的所有条目应该反映出这些更改,请忽略该更新的已关闭订单。就订单而言,这将是一个相当小的更新-允许一个用户拥有多少个未结订单? 10个? 20吗50?
定期清理用户数据,以防它们发生更改但从不订购任何内容-这些 User 条目会被删除。
您可以通过这种方式确保数据库级别的完整性-如果您要为用户添加报告,这些用户如果要更改其详细信息,则每天要进行三次(或明智地限制这些更改)。
您的大多数用户字段也应该是1:N关系-我至少有3个电话号码,并且可能使用2个地址(居住和运输),并且给出的电子邮件数超过1个。
使用“活动”将它们扩展到自己的表中可能会消除创建完整用户副本的需要。基于商务需求,可能需要记住用于运送物品的“地址”,而不是用于订购的手机号码或用于向其发送确认的电子邮件,但这只是一个商业决定。
除非您拥有一家非常活跃的商店,其中有数百万个不同的用户,每个用户订购数百个订单,并且每个月要更改二十次详细信息,否则您在使用当前最先进的数据库系统时都不会遇到问题。在我看来,这是一个基于实际需求的思想实验?