设计其中有不同的产品定义表的"订单"模式

时间:2020-03-06 14:35:52  来源:igfitidea点击:

这些年来,我在多个地方都看到过这种情况。我想知道是否还有其他人遇到了比我更好的解决方案...

我的公司销售的产品数量相对较少,但是我们销售的产品具有很高的专业性(即,为了选择给定的产品,必须提供大量的详细信息)。问题在于,尽管选择给定产品所需的细节量相对恒定,但所需的细节种类在产品之间差异很大。例如:

产品X可能具有识别特征,例如(假设)

  • '颜色',
  • '材料'
  • "平均失败时间"

但是产品Y可能具有特征

  • '厚度',
  • '直径'
  • '能量源'

创建同时使用产品X和产品Y的订单系统的问题(无论如何,其中之一)是订单行必须在某个时候引用其"销售"内容。由于在两个不同的表中定义了产品X和产品Y,并且不能使用宽表方案对产品进行非规范化(产品定义非常深),因此很难找到一种清晰的方式来定义订单行订单输入,编辑和报告非常实用。

我过去尝试过的事情

  • 使用产品X和产品Y的公用列创建一个名为"产品"的父表,然后使用"产品"作为OrderLine表的引用,并创建一个以"产品"为产品X的表之间主关系的FK关系基本上将"产品"表放置在OrderLine和所有不同的产品表(例如,产品X和Y)的父表中。它可以很好地用于订单输入,但是由于"产品"记录必须跟踪产品的种类以便确定如何将"产品"加入其更详细的子级(产品X或者产品),因此会导致订单报告或者编辑出现问题。 Y.优点:关键关系得以保留。缺点:报告,在订单行/产品级别进行编辑。
  • 在订单行级别创建"产品类型"和"产品关键字"列,然后使用一些CASE逻辑或者视图确定该行引用的定制产品。这类似于项目(1),但没有共同的"产品"表。我认为这是一种"快捷又肮脏"的解决方案,因为它完全消除了订单行及其产品定义之间的外键。优点:快速解决方案。缺点:与项目(1)相同,另外还有RI丢失。
  • 通过创建公共头表并将键/值对用于自定义属性(OrderLine [n] <-[1] Product [1] <-[n] ProductAttribute),对产品定义进行均化。优点:保留了关键关系;关于产品定义没有歧义。缺点:报告(例如,检索具有其属性的产品列表),属性值的数据类型,性能(获取产品属性,插入或者更新产品属性等)

如果其他人尝试了其他策略并获得更大的成功,我肯定会听到它。

谢谢你。

解决方案

这可能会让我们入门。它将需要一些改进

Table Product ( id PK, name, price, units_per_package)
Table Product_Attribs (id FK ref Product, AttribName, AttribValue)

这样我们就可以将属性列表添加到产品上。 -这实际上是选择3

如果我们知道最大数量的属性,则可以

Table Product (id PK, name, price, units_per_package, attrName_1, attrValue_1 ...)

当然,这会取消数据库的规范化,但会使查询更容易。

我更喜欢第一种选择,因为

  • 它支持任意数量的属性。
  • 可以将属性名称存储在另一个表中,并强制执行参照完整性,以便那些该死的加拿大人不会在其中粘贴"颜色"并破坏报告。

产品线有变化吗?
如果确实如此,那么为每个产品创建一个表将使我们付出高昂的代价,并且键/值对的想法将为我们提供良好的服务。这就是我自然被吸引的方向。

我将创建这样的表:

Attribute(attribute_id, description, is_listed)    
-- contains values like "colour", "width", "power source", etc. 
-- "is_listed" tells us if we can get a list of valid values: 

AttributeValue(attribute_id, value)
-- lists of valid values for different attributes.  

Product (product_id, description)

ProductAttribute (product_id, attribute_id)  
-- tells us which attributes apply to which products

Order (order_id, etc)

OrderLine (order_id, order_line_id, product_id)

OrderLineProductAttributeValue (order_line_id, attribute_id, value)
-- tells us things like: order line 999 has "colour" of "blue"

将它们组合在一起的SQL并不是很简单,但是也不是太复杂...并且大多数将被写入一次并保留(在存储过程或者数据访问层中)。

我们对许多类型的实体执行类似的操作。

克里斯(Chris)和艾杰(AJ):感谢回复。产品线可能会更改,但我不会将其称为"易失性"。

我不喜欢第三个选项的原因是,这是以产品属性值的元数据为代价的。它实际上将列转换为行,从而失去了该过程中数据库列的大多数优势(数据类型,默认值,约束,外键关系等)。

我实际上已经参与了以前的项目,其中产品定义是通过这种方式完成的。我们实质上创建了一个完整的产品/产品属性定义系统(数据类型,最小/最大出现次数,默认值,"必需"标志,使用场景等。)该系统最终可以运行,但在开销和性能上却付出了可观的代价(例如,用于形象化产品的物化视图,用于表示和验证用于产品定义的数据输入UI的自定义"智能"组件,用于在订单行上表示产品实例的可自定义属性的另一个"智能"组件(等等)。

再次感谢回复!

如果要维护数据完整性,并且产品类型相对较少并且很少添加新产品类型,则描述的第一个解决方案是最好的。这是我根据情况选择的设计。仅当报告需要特定于产品的属性时,报告才很复杂。如果报告仅需要公共"产品"表中的属性,就可以了。

我们描述的第二个解决方案称为"多态关联",这不好。"外键"不是真正的外键,因此我们不能使用DRI约束来确保数据完整性。 OO多态在关系模型中没有类似物。

我们描述的第三个解决方案涉及将属性名称存储为字符串,这是一个称为" Entity-Attribute-Value"的设计,我们可以说这是一个痛苦且昂贵的解决方案。没有办法确保数据的完整性,没有办法使一个属性"非空",也没有办法确保给定的产品具有一组特定的属性。没有办法将一个属性限制为一个查找表。在SQL中无法执行多种类型的聚合查询,因此我们必须编写大量应用程序代码才能生成报告。仅在必要时才使用EAV设计,例如,如果我们有无限数量的产品类型,每行的属性列表可能不同,并且架构必须经常容纳新的产品类型,而无需更改代码或者架构。

另一个解决方案是"单表继承"。这使用一个非常宽的表,其中每个产品的每个属性都有一个列。在与给定行上的产品无关的列中保留NULL。这实际上意味着我们不能将属性声明为NOT NULL(除非它在所有产品通用的组中)。而且,大多数RDBMS产品都限制单个表中的列数或者行的总宽度(以字节为单位)。因此,我们可以用这种方式表示的产品类型数量有限。

存在混合解决方案,例如,我们可以正常地将常用属性存储在列中,而将特定于产品的属性存储在Entity-Attribute-Value表中。或者,我们可以以其他结构化方式(例如XML或者YAML)将特定于产品的属性存储在Products表的BLOB列中。但是这些混合解决方案会受到影响,因为现在必须以其他方式获取某些属性

对于这种情况,最终的解决方案是使用语义数据模型,而使用RDF而不是关系数据库。这与EAV具有一些共同之处,但更具雄心。所有元数据的存储方式与数据相同,因此每个对象都是自描述的,我们可以查询给定产品的属性列表,就像查询数据一样。存在一些特殊产品(例如Jena或者Sesame)来实现此数据模型和一种不同于SQL的特殊查询语言。

我们不会忽略任何不可思议的子弹。

我们拥有有时称为"不相交子类"的东西。有一个带有两个子类(ProductX)和(ProductY)的超类(Product)。对于关系数据库来说,这确实是一个难题。 [另一个难题是物料清单。另一个难题是"节点和弧线图"。]

我们确实想要多态性,其中OrderLine链接到Product的子类,但不知道(或者关心)哪个特定的子类。

我们没有太多的建模选择。我们几乎已经确定了每个功能的缺点。这几乎是整个选择范围。

  • 将所有内容推向超类。这是单表方法,我们可以在Product中使用一个区分符(type =" X"和type =" Y")和一百万列。 Product的列是ProductX和ProductY中的列的并集。由于未使用的列,整个位置将为null。
  • 将所有内容下推到子类中。在这种情况下,我们需要一个视图,该视图是ProductX和ProductY的并集。该视图是为了创建完整订单而加入的。类似于第一个解决方案,只是它是动态构建的,并且优化效果不佳。
  • 将超类实例加入子类实例。在这种情况下,Product表是ProductX和ProductY列的交集。每个产品在ProductX或者ProductY中都有对键的引用。

确实没有一个大胆的新方向。在关系数据库的世界观中,这些就是选择。

但是,如果我们选择更改构建应用程序软件的方式,则可以摆脱此陷阱。如果应用程序是面向对象的,则可以使用一流的多态对象来完成所有操作。我们必须从笨拙的关系处理中进行映射。这种情况发生了两次:一次是当我们从数据库中获取内容以创建对象时,一次是将对象持久化回数据库中。

好处是我们可以简洁,正确地描述处理过程。作为对象,具有子类关系。

缺点是SQL会简化为批量获取,更新和插入操作。

当将SQL隔离到ORM层中并作为一种琐碎的实现细节进行管理时,这将成为一个优势。 Java程序员使用iBatis(或者Hibernate或者TopLink或者Cocoon),Python程序员使用SQLAlchemy或者SQLObject。 ORM执行数据库获取并保存;应用程序可以直接处理订单,行和产品。