如何为每种产品都有很多参数的多种产品设计产品表

时间:2009-03-30 01:19:06

标签: database-design relational-database database-schema

我在桌面设计方面没有太多经验。我的目标是创建一个或多个满足以下要求的产品表:

  • 支持多种产品(电视,手机,PC等)。每种产品都有不同的参数集,例如:

    • 手机将具有颜色,尺寸,重量,操作系统......

    • PC将拥有CPU,HDD,RAM ......

  • 参数集必须是动态的。您可以添加或编辑您喜欢的任何参数。

如果没有针对每种产品的单独表格,我如何满足这些要求?

4 个答案:

答案 0 :(得分:206)

至少有这五个选项可用于对您描述的类型层次结构进行建模:

  • Single Table Inheritance:所有产品类型的一个表,具有足够的列来存储所有类型的所有属性。这意味着很多列,其中大多数在任何给定行都是NULL。

  • Class Table Inheritance:一个产品表,存储所有产品类型共有的属性。然后是每种产品类型一个表,存储特定于该产品类型的属性。

  • Concrete Table Inheritance:没有常用Products属性的表。相反,每种产品类型一个表,存储常见的产品属性和产品特定的属性。

  • Serialized LOB:一个产品表,存储所有产品类型共有的属性。一个额外的列以XML,YAML,JSON或其他格式存储半结构化数据的BLOB。此BLOB允许您存储特定于每种产品类型的属性。您可以使用精美的设计模式来描述这一点,例如Facade和Memento。但是,无论你有多少属性都无法在SQL中轻松查询;你必须将整个blob提取回应用程序并将其排序。

  • Entity-Attribute-Value:一个用于Products的表,一个用于将属性转换为行而不是列的表。 EAV不是关于关系范式的有效设计,但无论如何许多人都使用它。这是另一个答案提到的“属性模式”。有关一些陷阱,请参阅StackOverflow上eav tag的其他问题。

我在演示文稿Extensible Data Modeling中写了更多关于此内容的文章。


关于EAV的其他想法:尽管许多人似乎更喜欢EAV,但事实并非如此。它似乎是最灵活的解决方案,因此也是最好的解决方案。但是,请记住格言TANSTAAFL。以下是EAV的一些缺点:

  • 无法强制列(相当于NOT NULL)。
  • 无法使用SQL数据类型来验证条目。
  • 无法确保属性名称拼写一致。
  • 无法将外键放在任何给定属性的值上,例如查找表。
  • 在传统的表格布局中获取结果既复杂又昂贵,因为要从多行获取属性,您需要为每个属性执行JOIN

EAV给你的灵活程度需要在其他方面做出牺牲,可能会使你的代码变得复杂(或更糟),而不是以更传统的方式解决原始问题。

在大多数情况下,没有必要具备这种程度的灵活性。在OP关于产品类型的问题中,为产品特定属性创建每种产品类型的表格要简单得多,因此您至少对同一产品类型的条目执行了一些一致的结构。

只有在必须允许每一行可能具有一组不同的属性时才使用EAV。如果您拥有一组有限的产品类型,EAV就会过度。 Class Table Inheritance将是我的首选。


更新2019:我越是看到人们使用JSON作为“许多自定义属性”问题的解决方案,我越不喜欢该解决方案。它使查询过于复杂,即使使用特殊的JSON functions来支持它们也是如此。存储JSON文档需要更多的存储空间,而不是存储在正常的行和列中。

基本上,这些解决方案在关系数据库中都不容易或有效。拥有“变量属性”的整个想法与关系理论基本上不一致。

归根结底是您必须选择其中一种解决方案,这对您的应用程序来说是最不利的。因此,在选择数据库设计之前,您需要知道如何查询数据。没有办法选择一个“最佳”的解决方案,因为任何解决方案可能最适合给定的应用程序。

答案 1 :(得分:13)

@StoneHeart

我会一直带着EAV和MVC来这里。

@Bill Karvin

  

以下是一些缺点   EAV:

No way to make a column mandatory (equivalent of NOT NULL).
No way to use SQL data types to validate entries.
No way to ensure that attribute names are spelled consistently.
No way to put a foreign key on the values of any given attribute, e.g.
     

查找表。

你在这里提到的所有事情:

  • 数据验证
  • 属性名称拼写验证
  • 必填列/字段
  • 处理依赖属性的破坏
在我看来,

根本不属于数据库,因为没有一个数据库能够像应用程序的编程语言那样在适当的级别上处理这些交互和需求。

在我看来,以这种方式使用数据库就像用一块石头敲钉子一样。你可以用岩石来做,但是你不想使用更精确且专门针对这种活动设计的锤子吗?

  

在传统的表格布局中获取结果非常复杂   昂贵,因为要获得属性   从多行你需要做JOIN   对于每个属性。

通过对部分数据进行少量查询并使用您的应用程序将其处理为表格布局,可以解决此问题。即使您有600GB的产品数据,如果您需要此表中每一行的数据,也可以批量处理。

更进一步如果您想提高查询的效果,可以选择某些操作,例如:报告或全局文本搜索并为它们做准备,这些索引表将存储所需数据并定期重新生成,每30分钟一次。

您甚至不需要担心额外数据存储的成本,因为它每天都会变得更便宜,更便宜。

如果您仍然关注应用程序执行的操作性能,您可以始终使用Erlang,C ++,Go语言预处理数据,然后再在主应用程序中进一步处理优化数据。

答案 2 :(得分:5)

如果我使用Class Table Inheritance含义:

  

一个产品表,存储所有产品类型共有的属性。然后是每个产品类型一个表,存储特定于该产品类型的属性。   -Bill Karwin

我最喜欢Bill Karwin的建议。我可以预见到一个缺点,我将尝试解释如何避免成为一个问题。

当一个只对1种类型共有的属性然后变为2,然后是3等时,我应该采取什么应急计划?

例如:(这只是一个例子,不是我真正的问题)

如果我们销售家具,我们可能会销售椅子,灯具,沙发,电视等。电视类型可能是我们携带的唯一具有功耗的类型。所以我会将power_consumption属性放在tv_type_table上。但随后我们开始携带家庭影院系统,这些系统也具有power_consumption属性。好吧,它只是另一个产品,所以我会将此字段添加到stereo_type_table,因为这可能是最简单的。但随着时间的推移,随着我们开始携带越来越多的电子产品,我们意识到power_consumption足够广泛,应该在main_product_table。我现在该怎么办?

将字段添加到main_product_table。编写一个脚本循环遍历电子设备,并将每个type_table的正确值放到main_product_table。然后从每个type_table中删除该列。

现在如果我总是使用相同的GetProductData类与数据库进行交互以获取产品信息;那么如果代码中的任何更改现在需要重构,那么它们应该仅适用于该类。

答案 3 :(得分:3)

您可以拥有一个Product表和一个包含3列的单独ProductAdditionInfo表:产品ID,附加信息名称,附加信息值。如果许多但不是所有类型的产品都使用了颜色,那么您可以将它作为Product表中的可为空的列,或者将其放在ProductAdditionalInfo中。

这种方法不是关系数据库的传统技术,但我已经看到它在实践中使用了很多。它可以灵活且具有良好的性能。

Steve Yegge称之为the Properties pattern并写了一篇关于使用它的长篇文章。