这是我多年来在多个地方看到的情景;我想知道是否有其他人遇到过比我更好的解决方案......
我的公司销售的产品数量相对较少,但我们销售的产品是高度专业化的(即为了选择给定的产品,必须提供大量的细节)。问题是,虽然选择给定产品所需的金额相对恒定,但所需细节的种在产品之间差异很大。例如:
产品X可能具有识别特征,如(假设)
但产品Y可能具有特征
创建同时使用产品X和产品Y的订单系统时,问题(其中之一,无论如何)是订单行必须在某个时候引用它“销售”的内容。由于产品X和产品Y在两个不同的表中定义 - 并且使用宽表格方案的产品非规范化不是一种选择(产品定义非常深) - 很难看到在这样的定义订单行的明确方法订单输入,编辑和报告的实用方式。
我过去曾尝试过的事情
如果其他人尝试了不同的策略并取得了更大的成功,我肯定希望听到它。
谢谢。
答案 0 :(得分:5)
如果您想保持数据完整性,并且您的产品类型相对较少且很少添加新产品类型,那么您描述的第一个解决方案是最好的。这是我在你的情况下选择的设计。仅当您的报告需要特定于产品的属性时,报告才会很复杂。如果您的报告只需要公共Products表中的属性,那就没关系。
您描述的第二种解决方案称为“多态关联”,这并不好。您的“外键”不是真正的外键,因此您不能使用DRI约束来确保数据完整性。 OO多态性在关系模型中没有类比。
您描述的第三个解决方案,涉及将属性名称存储为字符串,是一种名为“实体 - 属性 - 值”的设计,您可以说这是一个痛苦且昂贵的解决方案。无法确保数据完整性,无法使一个属性为NOT NULL,无法确保给定产品具有某组属性。无法针对查找表限制一个属性。在SQL中无法进行许多类型的聚合查询,因此您必须编写大量应用程序代码来执行报告。只有在必须时才使用EAV设计,例如,如果您拥有无限数量的产品类型,则每行的属性列表可能不同,并且您的架构必须经常适应新的产品类型,而无需更改代码或架构。
另一种解决方案是“单表继承”。这使用了一个非常宽的表,每个产品的每个属性都有一列。在与给定行上的产品无关的列中保留NULL。这实际上意味着您不能将属性声明为NOT NULL(除非它在所有产品的公共组中)。此外,大多数RDBMS产品对单个表中的列数或行的总宽度(以字节为单位)有限制。因此,您可以用这种方式表示的产品类型数量有限。
存在混合解决方案,例如,您可以在列中正常存储公共属性,但在Entity-Attribute-Value表中存储特定于产品的属性。或者,您可以在Products表的BLOB列中以其他结构化方式(如XML或YAML)存储特定于产品的属性。但是这些混合解决方案受到影响,因为现在必须以不同的方式获取某些属性
这种情况的最终解决方案是使用语义数据模型,使用RDF而不是关系数据库。这与EAV有一些共同点,但它更加雄心勃勃。所有元数据都以与数据相同的方式存储,因此每个对象都是自描述的,您可以像查询数据一样查询给定产品的属性列表。存在特殊产品,例如Jena或Sesame,实现此数据模型以及与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 ...)
这当然会使数据库失真,但会使查询更容易。
我更喜欢第一个选项,因为
答案 2 :(得分:2)
没有你忽略的神奇子弹。
你有时被称为“不相交的子类”。有超类(Product)有两个子类(ProductX)和(ProductY)。对于关系数据库来说,这是一个真正困难的问题。 [另一个难题是物料清单。另一个难题是节点和弧图。]
你真的想要多态,其中OrderLine链接到Product的子类,但不知道(或关心)哪个特定的子类。
您没有太多的建模选择。你几乎已经确定了每个的不良特征。这几乎是整个选择的范围。
将所有内容推送到超类。这是单表方法,你有一个带有鉴别器(type =“X”和type =“Y”)和一百万列的Product。 Product列是ProductX和ProductY中列的并集。由于未使用的列,整个地方都会出现空值。
将所有内容推送到子类中。在这种情况下,您需要一个视图,它是ProductX和ProductY的并集。这种观点是为了创造一个完整的订单而加入的。这就像第一个解决方案,除了它是动态构建的并且不能很好地优化。
将Superclass实例加入子类实例。在这种情况下,Product表是ProductX和ProductY列的交集。每个产品都引用了ProductX或ProductY中的密钥。
没有一个大胆的新方向。在关系数据库世界视图中,这些是选择。
但是,如果您选择更改构建应用程序软件的方式,则可以摆脱此陷阱。如果应用程序是面向对象的,那么您可以使用一流的多态对象完成所有操作。你必须从那种笨重的关系处理中进行映射;这种情况发生了两次:一次是从数据库中获取东西以创建对象,一次是将对象持久化回数据库。
优点是您可以简洁而正确地描述您的处理。作为对象,具有子类关系。
缺点是您的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)
我不喜欢第三种选择的原因是它以产品属性值的元数据为代价。它实际上将列转换为行,从而失去了进程中数据库列的大部分优点(数据类型,默认值,约束,外键关系等)。
我实际参与了过去的项目,其中产品定义以这种方式完成。我们基本上创建了一个完整的产品/产品属性定义系统(数据类型,最小/最大出现次数,默认值,'必需'标志,使用场景等)。系统最终起作用,但是在开销和性能方面成本很高(例如,物化视图以可视化产品,自定义“智能”组件来表示和验证产品定义的数据输入UI,另一个“智能”组件来表示产品实例在订单行上的可自定义属性,blahblahblah。
再次感谢您的回复!