163

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

  • 支持多种产品(电视,电话,PC,...)。每种产品都有一组不同的参数,例如:

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

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

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

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

4

4 回答 4

264

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

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

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

  • 具体表继承:没有通用产品属性的表。相反,每个产品类型都有一个表,存储常见的产品属性和产品特定的属性。

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

  • Entity-Attribute-Value:一张表用于产品,一张表将属性转为行,而不是列。EAV 就关系范式而言不是一个有效的设计,但无论如何很多人都在使用它。这是另一个答案提到的“属性模式”。请参阅 StackOverflow 上带有eav 标签的其他问题,了解其中的一些陷阱。

我在演示文稿“可扩展数据建模”中写了更多关于此的内容。


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

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

EAV 为您提供的灵活性程度需要在其他方面做出牺牲,这可能会使您的代码与以更传统的方式解决原始问题一样复杂(或更糟)。

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

仅当必须允许每一行都可能具有一组不同的属性时,我才会使用 EAV 。当您拥有一组有限的产品类型时,EAV 是多余的。类表继承将是我的首选。


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

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

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

于 2009-03-30T02:41:21.117 回答
13

@铁石心肠

我会一直使用 EAV 和 MVC。

@比尔卡文

以下是 EAV 的一些缺点:

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

您在这里提到的所有这些事情:

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

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

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

在传统的表格布局中获取结果既复杂又昂贵,因为要从多行获取属性,您需要为每个属性执行 JOIN。

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

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

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

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

于 2011-03-21T14:20:19.457 回答
7

如果我使用Class Table Inheritance含义:

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

我最喜欢 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类与数据库交互来提取产品信息;那么如果代码中的任何更改现在需要重构,它们应该只针对该类。

于 2010-09-16T19:31:34.093 回答
4

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

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

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

于 2009-03-30T01:44:33.030 回答