设计一个“订单”模式,其中有不同的产品定义表

时间:2008-09-23 13:16:43

标签: database database-design entity-attribute-value

这是我多年来在多个地方看到的情景;我想知道是否有其他人遇到过比我更好的解决方案......

我的公司销售的产品数量相对较少,但我们销售的产品是高度专业化的(即为了选择给定的产品,必须提供大量的细节)。问题是,虽然选择给定产品所需的金额相对恒定,但所需细节的在产品之间差异很大。例如:

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

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

但产品Y可能具有特征

  • '厚度',
  • '直径'
  • '电源'

创建同时使用产品X和产品Y的订单系统时,问题(其中之一,无论如何)是订单行必须在某个时候引用它“销售”的内容。由于产品X和产品Y在两个不同的表中定义 - 并且使用宽表格方案的产品非规范化不是一种选择(产品定义非常深) - 很难看到在这样的定义订单行的明确方法订单输入,编辑和报告的实用方式。


我过去曾尝试过的事情

  • 创建一个名为“Product”的父表,其中包含Product X和Product Y的通用列,然后使用“Product”作为OrderLine表的引用,并创建与“Product”作为表之间主要边的FK关系对于产品X和产品Y.这基本上将'Product'表作为OrderLine的父级和所有不同的产品表(例如Products X和Y)。它适用于订单输入,但会导致订单报告或编辑出现问题,因为“产品”记录必须跟踪它是什么类型的产品,以确定如何将“产品”加入其更详细的子产品,产品X或产品Y. 优势:保留关键关系。 缺点:报告,在订单行/产品级别进行编辑。
  • 在订单行级别创建“产品类型”和“产品密钥”列,然后使用一些CASE逻辑或视图来确定该行所引用的自定义产品。这类似于第(1)项,没有通用的“产品”表。我认为这是一个更“快速和肮脏”的解决方案,因为它完全取消了订单行和产品定义之间的外键。 优势:快速解决方案。 缺点:与第(1)项相同,加上丢失RI。
  • 通过创建公共标头表并使用自定义属性的键/值对(OrderLine [n]< - [1] Product [1]< - [n] ProductAttribute)来均衡产品定义。 优势:保持关键关系;没有关于产品定义的含糊不清。 缺点:报告(例如,检索具有其属性的产品列表),属性值的数据类型,性能(获取产品属性,插入或更新产品属性等)。

如果其他人尝试了不同的策略并取得了更大的成功,我肯定希望听到它。

谢谢。

5 个答案:

答案 0 :(得分:5)

如果您想保持数据完整性,并且您的产品类型相对较少且很少添加新产品类型,那么您描述的第一个解决方案是最好的。这是我在你的情况下选择的设计。仅当您的报告需要特定于产品的属性时,报告才会很复杂。如果您的报告只需要公共Products表中的属性,那就没关系。

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

您描述的第三个解决方案,涉及将属性名称存储为字符串,是一种名为“实体 - 属性 - 值”的设计,您可以说这是一个痛苦且昂贵的解决方案。无法确保数据完整性,无法使一个属性为NOT NULL,无法确保给定产品具有某组属性。无法针对查找表限制一个属性。在SQL中无法进行许多类型的聚合查询,因此您必须编写大量应用程序代码来执行报告。只有在必须时才使用EAV设计,例如,如果您拥有无限数量的产品类型,则每行的属性列表可能不同,并且您的架构必须经常适应新的产品类型,而无需更改代码或架构。

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

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

这种情况的最终解决方案是使用语义数据模型,使用RDF而不是关系数据库。这与EAV有一些共同点,但它更加雄心勃勃。所有元数据都以与数据相同的方式存储,因此每个对象都是自描述的,您可以像查询数据一样查询给定产品的属性列表。存在特殊产品,例如JenaSesame,实现此数据模型以及与SQL不同的特殊查询语言。

答案 1 :(得分:2)

这可能会让你开始。它需要一些改进

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 ...)

这当然会使数据库失真,但会使查询更容易。

我更喜欢第一个选项,因为

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

答案 2 :(得分:2)

没有你忽略的神奇子弹。

你有时被称为“不相交的子类”。有超类(Product)有两个子类(ProductX)和(ProductY)。对于关系数据库来说,这是一个真正困难的问题。 [另一个难题是物料清单。另一个难题是节点和弧图。]

你真的想要多态,其中OrderLine链接到Product的子类,但不知道(或关心)哪个特定的子类。

您没有太多的建模选择。你几乎已经确定了每个的不良特征。这几乎是整个选择的范围。

  1. 将所有内容推送到超类。这是单表方法,你有一个带有鉴别器(type =“X”和type =“Y”)和一百万列的Product。 Product列是P​​roductX和ProductY中列的并集。由于未使用的列,整个地方都会出现空值。

  2. 将所有内容推送到子类中。在这种情况下,您需要一个视图,它是ProductX和ProductY的并集。这种观点是为了创造一个完整的订单而加入的。这就像第一个解决方案,除了它是动态构建的并且不能很好地优化。

  3. 将Superclass实例加入子类实例。在这种情况下,Product表是ProductX和ProductY列的交集。每个产品都引用了ProductX或ProductY中的密钥。

  4. 没有一个大胆的新方向。在关系数据库世界视图中,这些是选择。

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

    优点是您可以简洁而正确地描述您的处理。作为对象,具有子类关系。

    缺点是您的SQL转向简化的批量提取,更新和插入。

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

答案 3 :(得分: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并不是一件容易的事,但它也不是太复杂......而且大部分都会写一次并保留(在存储过程或数据访问层中)。

我们用许多类型的实体做类似的事情。

答案 4 :(得分:0)

克里斯和AJ:谢谢你的回复。产品线可能会发生变化,但我不会将其称为“易变”。

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

我实际参与了过去的项目,其中产品定义以这种方式完成。我们基本上创建了一个完整的产品/产品属性定义系统(数据类型,最小/最大出现次数,默认值,'必需'标志,使用场景等)。系统最终起作用,但是在开销和性能方面成本很高(例如,物化视图以可视化产品,自定义“智能”组件来表示和验证产品定义的数据输入UI,另一个“智能”组件来表示产品实例在订单行上的可自定义属性,blahblahblah。

再次感谢您的回复!