如何设计一个多种产品的产品表,其中每个产品有多个参数

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

  • 支持多种产品(电视,电话,个人电脑,...)。每种产品都有不同的参数,如:

    • 手机将有颜色,大小,重量,操作系统..。

    • PC 将有 CPU,硬盘,内存..。

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

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

63167 次浏览

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

这种方法对于关系数据库来说并不是一种传统的技术,但是我看到它在实践中得到了广泛的应用。它具有灵活性和良好的性能。

Steve Yegge 称之为 属性模式,并写了一篇关于使用它的长文。

您至少有以下五个选项来建模您所描述的类型层次结构:

  • 单表继承 : 所有 Product 类型的一个表,有足够的列来存储所有类型的所有属性。这意味着列的 很多,其中大部分在任何给定行上为 NULL。

  • 类表继承 : 一个 Products 表,存储所有产品类型的公共属性。然后为每个产品类型创建一个表,存储特定于该产品类型的属性。

  • 具体表继承 : 没有针对常见 Products 属性的表。相反,每个产品类型一个表,同时存储公共产品属性和特定于产品的属性。

  • 序列化 LOB : 一个 Products 表,存储所有产品类型的公共属性。一个额外的列以 XML、 YAML、 JSON 或其他格式存储半结构化数据的 BLOB。这个 BLOB 允许您存储特定于每种产品类型的属性。您可以使用奇特的设计模式来描述它,例如 Facade 和 Memento。但是无论如何,您都有一个不能在 SQL 中轻松查询的属性块; 您必须将整个块提取回应用程序并在那里对它进行排序。

  • Entity-Attribute-Value : 一个表用于 Products,另一个表将属性转换为行而不是列。就关系范式而言,EAV 并不是一个有效的设计,但是许多人还是会使用它。这是另一个答案提到的“属性模式”。请参阅关于 StackOverflow 的 收到的其他问题,了解其中的一些陷阱。

关于这一点,我已经在 可扩展数据建模演示文稿中写了更多的内容。


关于 EAV 的其他想法: 虽然很多人似乎喜欢 EAV,但我不喜欢。这似乎是最灵活的解决方案,因此也是最好的。然而,请记住这句格言 TANSTAAFL。以下是电动汽车的一些缺点:

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

EAV 给您带来的灵活性程度要求在其他方面做出牺牲,这可能使您的代码变得比用更传统的方法解决原始问题时更复杂(或更糟)。

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

只有在必须允许 每一排具有一组独特属性的情况下,我才会使用 EAV。当您有一组有限的产品类型时,EAV 是过度的。类表继承将是我的首选。


2019年更新: 我越是看到人们使用 JSON 作为“许多自定义属性”问题的解决方案,我就越不喜欢这个解决方案。它使得查询过于复杂,即使使用特殊的 JSON 函数来支持它们。与存储在普通行和列中相比,存储 JSON 文档需要更多的存储空间。

基本上,这些解决方案在关系数据库中都不是简单或有效的。拥有“可变属性”的整个概念从根本上与关系理论不一致。

归根结底,你必须选择其中一个解决方案的基础上是最不坏的 你的应用程序。因此,在选择数据库设计之前,您需要知道如何查询数据。没有办法选择一个“最好”的解决方案,因为任何解决方案都可能对给定的应用程序是最好的。

如果我使用 Class Table Inheritance的意思是:

一个 Products 表,存储所有产品类型的公共属性。然后为每个产品类型创建一个表,存储特定于该产品类型的属性。 比尔卡文

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

当一个属性只对一个类型通用,然后对2、3等变得通用时,我应该采取什么应急计划?

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

如果我们卖家具,我们可能卖椅子、灯、沙发、电视等。电视机类型可能是我们携带的唯一一种耗电的类型。因此,我将把 power_consumption属性放在 tv_type_table上。但是,我们开始进行家庭影院系统,也有一个 power_consumption的属性。OK 它只是一个其他的产品,所以我将添加这个字段到 stereo_type_table以及,因为这可能是最简单的在这一点上。但随着时间的推移,我们开始携带越来越多的电子产品,我们认识到,power_consumption是足够广泛的,它应该在 main_product_table。我现在该怎么办?

将字段添加到 main_product_table。编写一个脚本,通过电子循环,并把正确的值从每个 type_tablemain_product_table。然后从每个 type_table中删除该列。

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

@ StoneHeart

我会一路跟着 EAV 和 MVC。

@ Bill Karvin

下面是。的一些缺点 地区代表:

  • 无法强制列(相当于 NOTNULL)。
  • 无法使用 SQL 数据类型来验证条目。
  • 无法确保属性名称的拼写一致。
  • 无法将外键放在任何给定属性的值上,例如。 查找表。

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

  • 数据验证
  • 属性名称拼写验证
  • 强制性列/字段
  • 处理依赖属性的破坏

在我看来,根本不属于数据库,因为没有任何数据库能够像应用程序的编程语言那样在适当的层次上处理这些交互和需求。

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

在传统的表格布局中获取结果是复杂的 很昂贵,因为要获得属性 您需要从多个行执行 JOIN 操作 对于每个属性。

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

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

您甚至不必担心额外数据存储的成本,因为它每天都变得越来越便宜。

如果你仍然关心应用程序的操作性能,你可以使用 Erlang,C + + ,Go Language 来预处理数据,然后在你的主应用程序中进一步处理优化后的数据。