电子商务订单表。节省价格,还是使用审核/历史记录表?


13

我正在设计我的第一个电子商务模式。我已经阅读了一段时间,对an order_line_item和a 之间的关系有些困惑product

一个product可以被购买。它具有各种细节,但最重要的是unit_price

在客户购买产品时,An order_line_item具有product_id购买,quantity购买和购买时的外键unit_price

我读过的大部分内容都说unit_price上的order_line_item应该显式添加(即,不通过引用product_id)。这是有道理的,因为商店将来可能会更改价格,这会弄乱订单报告,跟踪,完整性等。

我不明白的是,为什么直接将unit_price值保存到order_line_item

创建记录unit_price变更记录的审计/历史记录表会更好product吗?

order_line_item创建an时,将product_audit添加表的外键,并可以从此处检索价格(通过引用)。

在我看来,使用这种方法有很多好处(减少数据重复,更改价格历史记录等),那么为什么不更频繁地使用它呢?我没有遇到使用这种方法的电子商务模式的示例,我错过了什么吗?

UDPATE:看来我的问题与尺寸变化缓慢有关。我仍然很困惑,因为“缓慢变化的维度”与数据仓库和OLAP有关。那么,是否可以将“缓慢更改维度”类型应用于我的主要业务交易流程数据库(OLTP)?我想知道我是否将很多概念混在一起,将不胜感激一些指导。


实际上,我会同时做这两个事情:将销售价格存储在order_line中,存储产品价格的历史记录。因为两者都有不同的目的。存储销售价格将使对订单的查询变得更加轻松快捷。另一方面,您甚至可能想为尚未售出的产品获取旧价格。
a_horse_with_no_name 2014年

当然,简化订单查询并非每次节省最终价格的唯一动力。毕竟,它是一个关系数据库-查询不会那么困难。
Gaz_Edge 2014年

3
在订单行中存储销售价格不是“非关系”的(我认为它是规范化的,并且我认为销售价格是订单行的直接属性)。使用关系数据库的技巧是了解限制。如果您经营一家每天有100种产品和5个订单的商店,那么存储和检索旧价格不是问题。如果您正在运行一个拥有数百万种产品,每分钟成千上万的经销商和成千上万的订单的市场,那么查询历史记录是一个问题。
a_horse_with_no_name 2014年

您将在哪里存储历史价格?从我正在阅读的内容中,我需要两个数据库。一种用于OLTP,另一种用于OLAP。历史在OLAP中走了吗?
Gaz_Edge 2014年

Answers:


10

如您所知,将价格存储在订单上使技术实施更加容易。尽管有很多商业原因,但这样做可能会有所帮助。

除网络交易外,许多企业还通过其他渠道支持销售,例如:

  • 通过电话
  • 销售代理商“上路”
  • 在实际位置(例如商店,办公室)

在这些情况下,可以在交易发生后的某个时间将订单输入系统。在这种情况下,可能很难无法正确识别应使用哪个历史价格记录-将单价直接存储在订单上是唯一可行的选择。

多种渠道常常带来另一个挑战-同一产品的价格不同。电话定单的附加费很普遍-有些客户可能会讨价还价。您可能能够代表产品架构中所有渠道的所有可能价格,但是将其合并到订单表中可能会变得非常复杂。

凡允许进行谈判的地方,都很难将价格历史记录与商定的订单价格联系起来(除非代理商的谈判限制非常狭窄)。您需要将价格存储在订单本身上。

即使您仅支持网络交易并具有相对简单的定价结构,仍然需要克服一个有趣的问题-在飞行中如何应对价格上涨?企业是否坚持要求客户必须支付加价,还是他们遵守原价(将产品添加到购物篮中时)?如果是后者,则技术实​​施会很复杂-您需要找到一种方法来确保在会话中正确维护价格版本。

最后,许多企业开始使用高度动态的定价。给定产品可能没有一个固定的价格-它总是在运行时根据一天中的时间,产品需求等因素计算得出的。在这种情况下,价格可能不会一开始就针对产品而存储!


3

我将添加一些我已经看到的实用点。

  1. 产品是暂时的。

    他们今天所表示的含义可能与一年前所表示的含义不同。相同的SKU代码(以及因此的product_id)可能在不同阶段涉及产品的不同变体/种类。

    并不是每个人都了解眼前的所有担忧;因此,用户可以更改原始产品的特性,而不是凭自己的无知创造出新鲜的产品。很多时候,这可能是由于用户使用的计划而发生的(嘿!我只能拥有100个sku,所以为什么不继续更改旧版本而不是升级计划)所以,您看到了,在很多购物车中,产品永远不会表示同一件事。

  2. 根据订购和运输条件的不同价格

    正如@Chris用户所提到的,不同的价格可能适用于不同的情况。

    在大多数购物车中,您会发现至少存储了3个不同的字段-单价,折扣金额和折扣价。在更高级的产品中,您会发现另外2个产品-含税的单价,含税的折扣价。您可能会发现更多字段来描述运输方式费用和其他付款方式费用。税收百分比可能会因州,产品,国家/地区,运输方式等而异,其他成本来源也是如此。同样,折扣可以根据地理位置,促销,销售时间等而变化。因此,存在只能在订单级别获得的信息,并且不能仅从产品表中的数据生成此组合信息。

  3. 关注点分离

    大量的购物车以某种方式实现,因此不同的团队可以控制数据的不同部分。管理订单系统的人不一定总是需要知道所有库存的产品,不同时间点的价格,给定sku的替代品等等。将与产品相关的数据与订单数据一起保存,有助于实现关注点分离。如果不同的团队管理系统的不同部分,那么在开发阶段也是如此。

  4. 跨多个系统更容易扩展

    很多时候,订单管理系统,规则引擎,目录引擎,内容管理系统都是作为自己的独立系统构建/维护的。这有助于优化各种负载条件,并为每个系统生成专门的智能。因此,一个系统由于无法获得另一系统的信息而无法勒索。

  5. 更快的开发和运行时间

    我在这里使用了“开发时间”一词,尽管使用“调试时间”会更合适。每当发生任何新的开发时,如果所需的数据可用而又不增加其自身的复杂性,它将更快,因为这样,调试周期就会相对较小。

    想象一下,您被要求针对半年前给定月份的每日折扣生成按需报告。如果您有原始价格,1-2个表中的折扣价以及订单,订单商品详细信息,那么这很简单。但是,如果您必须从另一个表中获取价格,然后从另一个表中获取适用的折扣,然后找出详细信息,则开发和运行时间都将更长。

一个好的设计应该尽可能地对未来进行优化,对当前进行优化。


2

最终可能会花费更多的存储空间,但是我更喜欢将交易的所有相关详细信息包含在交易本身中,这样,无论出于何种原因,我们的审计跟踪都将被破坏,或者管理员会忽略现有的安全性,可以使用以下方式出售:所使用的货币,单价,数量,所应用的税种以及所产生的价值等。我通常将其存储为XML,以便在销售之间保持灵活。


编辑:要扩展我在上面简短地说过的内容,下面的后续评论以及上面提到的@a_horse_with_no_name,事务数据的冗余不仅很重要,而且在规模上也是必要的。

我假设您正在使用OOP进行扩展,因此您应该有一个交易对象以及一个包含所有产品对象和/或价格对象。以我自己的亲身经历,我宁愿在历史上变得冗长,存储相对便宜。

我们要做的是创建一个对象历史记录,您可以使用现有的RDBMS或某种形式的NOSQL键值存储(甚至更好的是允许NoSQL之类的RDBMS之类的连接,如handlersocket或memcache)来帮助您,并且我们存储对象历史记录这样,每个细节和价格的变化都可以在一个地方轻松,快速地获得。如果您很认真,甚至可以使用DIFF来节省存储空间,并且只存储更改,尽管它有其自身的警告。那应该照顾到您的历史记录,并且序列化对象的优点是您的系统将/应该能够将它们作为存储的对象进行备份。那会照顾历史。

关于我的建议,将交易详细信息(例如税金,货币等)与交易本身一起存储意味着无需在其他地方查看这些细节,您的交易对象将了解其属性,并且您的视图可以帮助呈现您认为合适的各种数据。您可以快速访问快照,并具有冗余和可验证记录的额外好处。

值得,相信我!


说您不使用它真的没有任何意义,因为它可以被删除。您可以覆盖任何数据。任何策略都应保留备份
Gaz_Edge 2014年

@Gaz_Edge它们不是互斥的,您仍然可以利用审核跟踪,并将详细信息存储在事务中,在这种情况下,冗余是合理的。切勿将数据遗忘给这一重要问题,而这会导致单点故障。在我们的案例中,我使用集中式对象存储作为历史记录机制,而不是尝试为每个对象类型构建历史记录(在这种情况下就像是交易或产品)。我将同时拥有整个交易对象的历史记录以及所有最重要的细节与记录本身。历史上完全没有产品对象。
oucil 2014年

如果我想运行订单报告怎么办?像已经购买了多少产品x?还是超过y数量的订单?另存为XML意味着您将失去查询数据的能力,除非我弄错了
Gaz_Edge 2014年

@Gaz_Edge不对,如果您正在使用MySQL,SELECT ExtractValue(field_name, '/x/path/');那么您可以进行过滤,例如使用特定货币的所有交易,或具有特定最低税值的所有交易等。可以从对象历史记录中完成更大范围的报告。对于较大规模的报告,您可以设置elasticsearch具有BigData样式报告的服务器/实例,并且可以轻松扩展到数百万个docs +。
oucil 2014年

@Gaz_Edge我还应该提到,您正在谈论的报告(购买价值超过一定的价格,产品的销售等)是常见的报告,这些报告应将其值与交易记录一起存储为列值,以便进行更快的处理。任何重要但不一定报告的内容都可以纳入XML。快照数据实际上仅用于两件事:1。尺寸变化缓慢问题; 2。当客户抱怨时进行验证和比较,您需要快速查看谁是对的。并非每天都在使用。
oucil 2014年

1

我的投票是将单价存储在您的订单项上,并在单独的表格中跟踪产品的定价历史记录。我这样做的理由是要增加灵活性。

即使您的定价结构僵化且定义明确,并且不允许上述@Chris Saxon进行更改,您是否会总是那样呢?即使您有信心,为什么还要在角落里画画呢?我认为将其存储在订单项详细信息中是一个好主意,因为我无法想到将其分开的有说服力的理由。

至于存储您的定价历史记录,将其单独存储具有一定的价值,因为项目的价格可能会发生变化,而没人会购买。知道价格变动是否无效,这绝对是有用的信息。如您所提到的,这是数据仓库场景中类型2缓慢变化的维度的经典用例。通常,将捕获产品表中的任何价格变化,并将新行添加到维度表中,其中包含更新的价格和时间戳,以指示何时发生此变化。上一行将更新结束日期,以指示它不再是有效价格。因此,一种方法是跟踪数据仓库中的此类更改。

但是,如果您不希望在设计OLTP电子商务数据库的同时设计数据仓库架构和ETL流程,那么可以肯定地在我们的电子商务数据库中捕获此历史记录。您可以通过创建一个单独的product_audit表来完成此操作,该表悬挂在product表之外,并包含该产品版本生效的开始日期和结束日期。也可以通过在产品表本身中添加开始日期和结束日期来指示当前处于活动状态的产品,来完成此操作。但是,根据公司所接受的产品数量和数量或价格变化,这可能会使您的产品表大大超出预期,并可能在以后导致查询性能问题。

最后,将您的定价历史记录与订单项的实际单价分开可以肯定会提供其他分析机会,以查看何时以高于或低于标价的价格出售产品。


感谢您的回答。我认为我要采用的策略是根据本书设计OLTP。我实际上很喜欢有一个用于报告的数据仓库的想法。尽管它增加了一些额外的工作(创建新的架构),但是该架构可以针对OLAP进行定制。有点避免了我试图在圆孔中安装方形钉的情况。
Gaz_Edge 2014年

@Gaz_Edge:在为我的新项目做出决定方面,我处于类似情况。您能否谈谈您一年前采用哪种设计方法的想法?运作良好吗?
绝对编码人员2015年

@CoderAbsolute我最初的解决方案非常面向“数据库”。我的应用程序现在围绕着面向服务的体系结构。我现在有许多小型的解耦数据库架构,而不是一种紧密耦合的数据库架构。现在已经不再需要在一个大规模架构中实现3N。因此,我现在将单价直接添加到订单中。由于这不是业务需求,因此保持历史产品价格变化现已消失。希望能有所帮助。
Gaz_Edge 2015年

0

我完全同意将与订单相关的信息(上下文)保持在一起的主要思想。仅需一点点说明,这种情况只有在您以数据库为中心设计应用程序并且一切都围绕大型db发生时才会出现。如果您从另一个角度查看问题域来切换观点,则您将清楚地观察到该顺序是应用程序生命周期中非常特殊事件的捕获快照。当您基于上下文处理问题时,数据库问题将成为次要问题,并且每个人都担心查询和生成报告的复杂性将在域模型中无缝处理。


一些小例子可以使您的答案更容易理解。尤其是诸如“订单是应用程序生命周期中非常特殊事件的捕获快照”之类的句子。
dezso
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.