产品属性列表设计模式


9

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

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

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

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

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

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

(Martin Fowler的书《企业应用程序体系结构的模式》称为“ 外键映射 ”)

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

对我而言,拉出特定产品时最终发生的连接数量过多。您必须将每个产品属性表都连接到产品,以便获得该属性的字段。通常,该字段的名称可能仅仅是字符串(varchar)。

这种设计模式最终会创建大量表,并且最终会为每个属性提供一个表。解决此问题的一种方法是为所有产品属性创建更多的“抓包”表。像这样:

product_attribute
----------------
attributeId (PK) 
name
field_name

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

1  red     color
2  blue    color
3  chrome  material
4  plastic material
5  yellow  color
6  x-large size

这可以帮助减少表的蠕变,但不会减少联接的数量,将这么多不同的类型组合到一个表中感觉有点不对。但是您将能够轻松获得所有可用的“颜色”属性。

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

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

Product             product_[type] 
----------------    ----------------
part_number (PK)    part_number (FK) 
UPC                 specific_attr1 
price               specific_attr2 
...                 ...

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

part_number    color    material
-----------    -----    --------
1234           red      plastic

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

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

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

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

因此,在我对这种情况进行了漫长的解释和分析之后,我意识到这不是一个罕见的情况,甚至可能为这种情况命名。

是否有普遍接受的解决方案来应对这一设计挑战?如果表相对较小,是否可能接受大量的联接?在某些情况下,是否可以接受属性名称而不是属性PK?我是否还在考虑其他解决方案?

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

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

1
您将拥有几种产品类型?
dezso 2012年

1
好问题。它会从3-4开始,但有可能逐渐扩大到10+
jmbertucci 2012年

“属性的授权列表”是什么意思?
NoChance 2012年

抱歉,它应该是“属性值”。您有一个表列出了属性允许的所有值的想法。就是 这是此产品类型可以选择的10种颜色的列表。这10个是某些人可以选择的“授权”值。
jmbertucci 2012年

我想知道是否所有这些属性值都加入到产品类型表中是否可以,如果我最终在其上创建一个“视图”呢?
jmbertucci 2012年

Answers:


17

我个人将使用类似于以下内容的模型:

产品表非常简单,您的主要产品详细信息:

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

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

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');

最后,将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);

根据要使用数据的方式,您可以查看两个联接:

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;

请参阅带有演示的SQL Fiddle。这将以以下格式返回数据:

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

但是,如果要以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;

请参阅带有演示的SQL Fiddle。数据以以下格式返回:

PART_NUMBER | NAME       | PRICE | COLOR | MATERIAL | SIZE
_________________________________________________________________
1           | product1   | 50    | red   | chrome   | null
2           | product2   | 96    | blue  | null     | x-large

如您所见,数据可能为您提供了更好的格式,但是如果您具有未知数量的属性,由于对属性名称进行硬编码,数据将很容易变得站不住脚,因此在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;

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


+1-一个奇妙的答案。在接受之前,我仍然需要花一些时间重新阅读并消化该答案。对于我有关联接和产品属性的问题,它看起来确实是一个很好的解决方案,甚至在枢轴和已准备好的语句示例之外也是如此。因此,我将从+1开始。=)
jmbertucci 2012年

@jmbertucci您似乎担心查询表,所以我想为您提供一些示例。:)
Taryn

确实。我要走了,因为我没有看到要对产品进行归类的交叉表。可能是过度考虑的情况,尤其是在沉浸了设计模式和理论之后。另外,我的DBA经验是基础知识,我需要做一些准备好的语句,因此,您的加入对您很有帮助。这个答案帮助打破了我遇到的“作家障碍”,因此我可以继续进行这个项目,这让我很开心。=)
jmbertucci 2012年

好吧,一个问题……这很慢吗?我摔倒了你就需要超过30秒,查询只10K产品具有10个属性..
ZenithS

@ZenithS您必须对其进行测试才能查看并可能在查询的列上添加索引。我没有MySQL实例可以进行任何测试。
塔林

0

我将扩展Taryn的答案并修改属性表,使其具有fk_attribute_type_id列,该列将代替attribute_name列,并指向新表attribute_type。

因此,您在一个表中具有结构化的属性类型,并且可以随时在一个位置中对其进行更改。

在我看来,最好使用“拨号”类的东西(具有可能类型的表)而不是枚举类型(例如,它在attribute_name列中(实际上,它不是名称,它的属性类型))。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.