【问题标题】:Designing an 'Order' schema in which there are disparate product definition tables设计一个“订单”模式,其中有不同的产品定义表
【发布时间】:2010-09-12 08:43:54
【问题描述】:

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

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

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

  • '颜色',
  • '材料'
  • “平均故障时间”

但产品 Y 可能具有特征

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

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


我过去尝试过的事情

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

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

谢谢。

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

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

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

    【讨论】:

      【解决方案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 进行数据库获取和保存;您的应用程序直接操作订单、行和产品。

      【讨论】:

        【解决方案3】:

        这可能会让您入门。它需要一些改进

        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. 属性名称可以存储在另一个表中,并强制执行引用完整性,这样那些该死的加拿大人就不会在其中粘贴“颜色”并破坏报告。

        【讨论】:

          【解决方案4】:

          您的产品线是否会发生变化?
          如果是这样,那么为每个产品创建一个表将花费您高昂的成本,而键/值对的想法将为您提供很好的服务。这就是我自然被吸引的那种方向。

          我会创建这样的表:

          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 不是微不足道的,但也不是太复杂......而且大部分将被写入一次并保存(在存储过程或您的数据访问层中)。

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

          【讨论】:

            【解决方案5】:

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

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

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

            再次感谢您的回复!

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-06-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-04-19
              相关资源
              最近更新 更多