7

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

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

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

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

但产品 Y 可能具有特征

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

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


我过去尝试过的事情

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

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

谢谢你。

4

5 回答 5

5

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

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

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

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

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

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

于 2008-09-23T18:26:21.743 回答
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. 属性名称可以存储在另一个表中,并强制执行引用完整性,以便那些该死的加拿大人不会在其中粘贴“颜色”并破坏报告。
于 2008-09-23T13:37:42.903 回答
2

没有你忽视的灵丹妙药。

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

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

您没有太多的建模选择。你几乎已经确定了每个的坏特性。这几乎是整个选择的世界。

  1. 把所有东西都推到超类上。这就是单表方法,您可以使用带有鉴别器(type="X" 和 type="Y")和一百万列的 Product。Product 的列是 ProductX 和 ProductY 中的列的并集。由于未使用的列,到处都会有空值。

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

  3. 将超类实例连接到子类实例。在这种情况下,Product 表是 ProductX 和 ProductY 列的交集。每个产品都有一个对 ProductX 或 ProductY 中的键的引用。

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

但是,如果您选择改变构建应用软件的方式,您就可以摆脱这个陷阱。如果应用程序是面向对象的,那么您可以使用一流的多态对象来做任何事情。您必须从那种笨拙的关系处理中进行映射;这种情况发生了两次:一次是从数据库中获取内容以创建对象,一次是您将对象持久化回数据库。

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

缺点是您的 SQL 会演变为简单的批量获取、更新和插入。

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

于 2008-09-23T18:44:05.163 回答
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 并不是微不足道的,但也不是太复杂……而且大部分将被编写一次并保留(在存储过程或数据访问层中)。

我们对多种类型的实体做类似的事情。

于 2008-09-23T13:44:56.273 回答
0

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

我不喜欢第三种选择的原因是它以产品属性值的元数据为代价。它本质上是把列变成行,在这个过程中失去了数据库列的大部分优势(数据类型、默认值、约束、外键关系等)

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

再次感谢您的回复!

于 2008-09-23T14:01:54.330 回答