产品属性列表设计模式

jmb*_*cci 11 mysql database-design

我正在更新我们网站的产品数据库。它内置于 MySQL 中,但这更像是一个通用的数据库设计模式问题。

我打算切换到超类型/子类型模式。我们当前/以前的数据库主要是单个表,其中包含有关单一类型产品的数据。我们正在考虑扩大我们的产品范围以包括不同的产品。

这个新的设计稿是这样的:

Product             product_[type]          product_attribute_[name]
----------------    ----------------        ----------------------------
part_number (PK)    part_number (FK)        attributeId (PK)
UPC                 specific_attr1 (FK)     attribute_name
price               specific_attr2 (FK)
...                 ...
Run Code Online (Sandbox Code Playgroud)

我有一个关于产品属性表的问题。这里的想法是产品可以具有给定属性的列表,例如颜色:红色、绿色、蓝色或材料:塑料、木材、铬、铝等。

此列表将存储在表中,该属性项的主键 (PK) 将在特定产品表中用作外键 (FK)。

(Martin Fowler 的著作Patterns of Enterprise Application Architecture称之为“外键映射”)

这允许网站界面拉取给定属性类型的属性列表,并在下拉选择菜单或其他一些 UI 元素中将其吐出。该列表可以被认为是属性值的“授权”列表。

拉动特定产品时最终发生的连接数量对我来说似乎过多。您必须将每个产品属性表连接到产品,以便您可以获取该属性的字段。通常,该字段可能只是名称的字符串 (varchar)。

这种设计模式最终会创建大量表,并且您最终会为每个属性创建一个表。抵消这种情况的一个想法是为所有产品属性创建一个更像是“抓包”表的东西。像这样的东西:

product_attribute
----------------
attributeId (PK) 
name
field_name
Run Code Online (Sandbox Code Playgroud)

这样,您的表可能如下所示:

1  red     color
2  blue    color
3  chrome  material
4  plastic material
5  yellow  color
6  x-large size
Run Code Online (Sandbox Code Playgroud)

这可以帮助减少表蠕变,但它不会减少连接的数量,而且将这么多不同类型组合到一个表中感觉有点错误。但是你可以很容易地获得所有可用的“颜色”属性。

但是,可能有一个属性具有比“名称”更多的字段,例如颜色的 RGB 值。这将要求该特定属性可能具有另一个表或具有用于名称:值对的单个字段(这有其自身的缺点)。

我能想到的最后一种设计模式是将实际属性值存储在特定产品表中,根本没有“属性表”。像这样的东西:

Product             product_[type] 
----------------    ----------------
part_number (PK)    part_number (FK) 
UPC                 specific_attr1 
price               specific_attr2 
...                 ...
Run Code Online (Sandbox Code Playgroud)

它将包含实际值,而不是另一个表的外键,例如:

part_number    color    material
-----------    -----    --------
1234           red      plastic
Run Code Online (Sandbox Code Playgroud)

这将消除连接并防止表蠕变(也许?)。但是,这会阻止拥有属性的“授权列表”。您可以返回给定字段(即:颜色)的所有当前输入值,但这也消除了为给定属性提供值的“授权列表”的想法。

要获得该列表,您仍然必须创建一个“抓包”属性表或为每个属性创建多个表(表蠕变)。

这造成了更大的缺点(以及为什么我从未使用过这种方法)现在在多个位置使用产品名称。

如果您在“主属性表”中具有“红色”的颜色值并将其存储在“product_[type]”表中,那么如果应用程序没有更新“主”表将导致潜在的数据完整性问题也不要使用“product_type”表中的旧值更新所有记录。

所以,在我对这个场景的冗长解释和分析之后,我意识到这不可能是一个罕见的场景,甚至可能有这种情况的名称。

对于这个设计挑战,是否有普遍接受的解决方案?如果表相对较小,潜在的大量连接是否可以接受?在某些情况下是否可以存储属性名称而不是属性 PK?还有我没有考虑的其他解决方案吗?

关于此产品数据库/应用程序的一些注意事项:

  • 产品不经常更新/添加/删除
  • 属性不经常更新/添加/删除
  • 该表是最常查询的读取/返回信息
  • 启用服务器端缓存以缓存给定查询/结果的结果
  • 我计划只从一种产品类型开始,然后随着时间的推移扩展/添加其他类型,并且可能会有 10 多种不同的类型

Tar*_*ryn 17

我个人会使用类似于以下的模型:

产品表将是非常基本的,您的主要产品详细信息:

create table product
(
  part_number int, (PK)
  name varchar(10),
  price int
);
insert into product values
(1, 'product1', 50),
(2, 'product2', 95.99);
Run Code Online (Sandbox Code Playgroud)

其次是属性表来存储每个不同的属性。

create table attribute
(
  attributeid int, (PK)
  attribute_name varchar(10),
  attribute_value varchar(50)
);
insert into attribute values
(1, 'color', 'red'),
(2, 'color', 'blue'),
(3, 'material', 'chrome'),
(4, 'material', 'plastic'),
(5, 'color', 'yellow'),
(6, 'size', 'x-large');
Run Code Online (Sandbox Code Playgroud)

最后创建 product_attribute 表作为每个产品及其关联属性之间的 JOIN 表。

create table product_attribute
(
  part_number int, (FK)
  attributeid int  (FK) 
);
insert into product_attribute values
(1,  1),
(1,  3),
(2,  6),
(2,  2),
(2,  6);
Run Code Online (Sandbox Code Playgroud)

根据您希望如何使用数据,您正在查看两个连接:

select *
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid;
Run Code Online (Sandbox Code Playgroud)

请参阅SQL Fiddle with Demo。这以以下格式返回数据:

PART_NUMBER | NAME       | PRICE | ATTRIBUTEID | ATTRIBUTE_NAME | ATTRIBUTE_VALUE
___________________________________________________________________________
1           | product1   | 50    | 1           | color          | red
1           | product1   | 50    | 3           | material       | chrome
2           | product2   | 96    | 6           | size           | x-large
2           | product2   | 96    | 2           | color          | blue
2           | product2   | 96    | 6           | size           | x-large
Run Code Online (Sandbox Code Playgroud)

但是,如果您想以一种PIVOT格式返回数据,其中一行包含所有属性作为列,您可以使用CASE带有聚合的语句:

SELECT p.part_number,
  p.name,
  p.price,
  MAX(IF(a.ATTRIBUTE_NAME = 'color', a.ATTRIBUTE_VALUE, null)) as color,
  MAX(IF(a.ATTRIBUTE_NAME = 'material', a.ATTRIBUTE_VALUE, null)) as material,
  MAX(IF(a.ATTRIBUTE_NAME = 'size', a.ATTRIBUTE_VALUE, null)) as size
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid
group by p.part_number, p.name, p.price;
Run Code Online (Sandbox Code Playgroud)

请参阅SQL Fiddle with Demo。数据以以下格式返回:

PART_NUMBER | NAME       | PRICE | COLOR | MATERIAL | SIZE
_________________________________________________________________
1           | product1   | 50    | red   | chrome   | null
2           | product2   | 96    | blue  | null     | x-large
Run Code Online (Sandbox Code Playgroud)

如您所见,数据可能采用更好的格式,但如果您有未知数量的属性,由于硬编码属性名称,它很容易变得站不住脚,因此在 MySQL 中,您可以使用准备好的语句来创建动态数据透视表. 您的代码如下(参见SQL Fiddle With Demo):

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MAX(IF(a.attribute_name = ''',
      attribute_name,
      ''', a.attribute_value, NULL)) AS ',
      attribute_name
    )
  ) INTO @sql
FROM attribute;

SET @sql = CONCAT('SELECT p.part_number
                    , p.name
                    , ', @sql, ' 
                   from product p
                   left join product_attribute t
                     on p.part_number = t.part_number
                   left join attribute a
                     on t.attributeid = a.attributeid
                   GROUP BY p.part_number
                    , p.name');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Run Code Online (Sandbox Code Playgroud)

这生成与第二个版本相同的结果,无需硬编码任何内容。虽然有很多方法可以对此进行建模,但我认为这种数据库设计是最灵活的。