如何为实体的自定义属性建模?

Mic*_*hal 5 sql sql-server schema database-design entity-attribute-value

假设我们有一个应该能够存储所有类型产品的应用程序.每个产品至少具有一个ID和一个Name但是所有其他属性可以由用户自己定义.

  1. 例如,他可以创建一个包含属性容量生成的产品组Ipods
  2. 例如,他可以创建一个具有属性大小颜色的产品组TShirts
  3. 我们需要存储产品的定义和具体的产品本身.
  4. 我们希望确保可以通过产品属性轻松聚合(GROUP BY).例如,选择每代ipod的容量总和
  5. 该解决方案不得要求架构更改(由于Bill Karwin的输入而增加了要求 - 请参阅他的答案!)

您如何根据上述要求对模式进行建模?

注意:要求4.很重要!

感谢大家的贡献和讨论方法.我在过去看到过这个问题的一些解决方案,但没有一个能让我轻松分组:(

Bil*_*win 11

我建议使用Concrete Table InheritanceClass Table Inheritance设计.两种设计都满足您的所有四个标准.

在具体表继承中:

  1. iPod的存储在表中product_ipods的列ID,Name,Capacity,Generation.
  2. T恤都存储在表中product_tshirts的列ID,Name,Size,Color.
  3. 具体的产品类型的定义是在的元数据(表定义)product_ipodsproduct_tshirts.
  4. SELECT SUM(Capacity) FROM product_ipods GROUP BY Generation;

在类表继承中:

  1. 通用产品属性存储在Products具有列的表IDName.

    iPod的存储在表中product_ipods的列product_id(外键Products.ID)Capacity,Generation.

  2. T恤都存储在表中product_tshirts的列product_id(外键Products.ID)Size,Color.
  3. 具体的产品类型的定义是在元数据的(表定义)products,product_ipodsproduct_tshirts.
  4. SELECT SUM(Capacity) FROM product_ipods GROUP BY Generation;

另请参阅我对" 产品表,多种产品,每种产品都有许多参数 "的回答,其中我描述了您所描述的问题类型的几种解决方案.我还详细介绍了为什么EAV是一个破碎的设计.


来自@dcolumbus的评论:

对于CTI,product_ipods的每一行都是一个变量,它的价格是多少?

products如果每种类型的产品都有价格,我希望价格列出现在表格中.对于CTI,产品类型表通常只包含仅适用于该类型产品的属性列.所有产品类型共有的任何属性都会获得父表中的列.

此外,在存储订单行项目时,您是否会将product_ipods中的行存储为订单项?

在行项目表中,存储产品ID,该值在products表和product_ipods表中应该是相同的值.


来自@dcolumbus的评论:

这对我来说似乎是多余的......在那种情况下,我没有看到子表的重点.但即使子表确实有意义,连接是id什么?

子表的要点是存储所有其他产品类型不需要的列.

连接id可以是自动递增数.子类型表不需要自动递增自己的id,因为它只能使用超级表生成的值.

CREATE TABLE products (
  product_id INT AUTO_INCREMENT PRIMARY KEY,
  sku VARCHAR(30) NOT NULL,
  name VARCHAR(100) NOT NULL,
  price NUMERIC(9,2) NOT NULL
);

CREATE TABLE product_ipods (
  product_id INT PRIMARY KEY,
  size TINYINT DEFAULT 16,
  color VARCHAR(10) DEFAULT 'silver',
  FOREIGN KEY (product_id) REFERENCES products(product_id)
);

INSERT INTO products (sku, name, price) VALUES ('IPODS1C1', 'iPod Touch', 229.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 16, 'silver');
INSERT INTO products (sku, name, price) VALUES ('IPODS1C2', 'iPod Touch', 229.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 16, 'black');
INSERT INTO products (sku, name, price) VALUES ('IPODS1C3', 'iPod Touch', 229.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 16, 'red');
INSERT INTO products (sku, name, price) VALUES ('IPODS2C1', 'iPod Touch', 299.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 32, 'silver');
INSERT INTO products (sku, name, price) VALUES ('IPODS2C2', 'iPod Touch', 299.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 32, 'silver');
INSERT INTO products (sku, name, price) VALUES ('IPODS2C3', 'iPod Touch', 299.00);
INSERT INTO product_ipods VALUES (LAST_INSERT_ID(), 32, 'red');
Run Code Online (Sandbox Code Playgroud)


Cad*_*oux 5

分组不会很容易,因为您将在“颜色”上使用什么聚合运算符?请注意,不可能在案例 2 上使用您的要求 4。

在任何情况下,聚合只是由于数据类型的变化而变得困难,并且可以通过以更类型安全的方式接近它来减轻聚合 - 知道添加苹果和橙子永远没有意义。

这是经典的 EAV 模型,它在精心设计的数据库中占有一席之地。为了使其更加类型安全,我见过将值存储在类型安全表中而不是单个自由格式 varchar 列中的情况。

而不是价值观:

EntityID int
,AttributeID int
,Value varchar(255)
Run Code Online (Sandbox Code Playgroud)

您有多个表:

EntityID int
,AttributeID int
,ValueMoney money

EntityID int
,AttributeID int
,ValueInt int

etc.
Run Code Online (Sandbox Code Playgroud)

然后获取每代 iPod 容量:

SELECT vG.ValueVarChar AS Generation, SUM(vC.ValueDecimal) AS TotalCapacity
FROM Products AS p
INNER JOIN Attributes AS aG
    ON aG.AttributeName = 'generation'
INNER JOIN ValueVarChar AS vG
    ON vG.EntityID = p.ProductID
    AND vG.AttributeID = aG.AttributeID
INNER JOIN Attributes AS aC
    ON aC.AttributeName = 'capacity'
INNER JOIN ValueDecimal AS vC
    ON vC.EntityID = p.ProductID
    AND vC.AttributeID = aC.AttributeID
GROUP BY vG.ValueVarChar
Run Code Online (Sandbox Code Playgroud)

  • 我想在这里指出,我通常会在实践中与几乎所有 EAV 设计作斗争,直到出现压倒性的使用需求为止。 (2认同)